Categories
Code iOS

Requesting reviews in iOS with SKStoreReviewController

If you don’t ask, you don’t get

Someone who’d not heard of bad App Store reviews

Use iOS APIs to ask your user to leave you a review – just like asking anyone for anything, you should try to do this when they’re in a good place. For example, tie it in to your user adding a photo or selecting a favourite.

First, import StoreKit:

import StoreKit

StoreKit has a requestReview() method which limits the request so your users aren’t bombarded with requests – for this reason, you shouldn’t attach the request to a button saying “Leave a review” or anything like that. iOS will decide when to show the request, and you should just let it do its thing.

In iOS 14, you’ll need to provide a scene parameter to the method, like so:

SKStoreReviewController.requestReview(in: scene)

That’s literally all you need to do.

I use this extension to grab the active scene in my apps:

extension UIApplication {
    var currentScene: UIWindowScene? {
        connectedScenes
            .first { $0.activationState == .foregroundActive } as? UIWindowScene
    }
}

Happy review requesting!

Categories
Code iOS Laravel

Credit where credit is due

This is part two of a completely unknown number of linked articles as I’m writing them without even the slightest bit of aforethought. You can find the first instalment here. They’re not tutorials as such, but more documenting my progress and thinking as I forge onward building something.

First thing to do is add a property to your User model in Laravel to represent the number of credits a user has. We’ll give a new user 5 credits to start with.

Schema::table('users', function (Blueprint $table) {
  $table->integer("credit_balance")->default(5);
});

Then a method on the User model to add credits to the users balance:

public function addCredits($credits){
  $this->credit_balance += $credits;
  $this->save();
}

Finally, a couple of API endpoints to retrieve the anonymous user’s current balance and top-up the account:

public function balance()
{
  return response()->json(["balance"=>Auth::user()->credit_balance], 200);
}

public function topUp(Request $request)
{
  $validation = Validator::make($request->all(),[
    'credits' => 'required',
  ]);

  if($validation->fails())
    return response()->json(["errors"=>$validation->messages()], 200);

  $user = Auth::user();
  $user->addCredits($request->credits);

  return response()->json(["balance"=>$user->credit_balance], 200);
}

Need to get this into the app now, so the user can see their credit balance. For this bit, I’m using Swift UI – if you haven’t played with it yet you ought to, it’s really cool!

I’ve created a BalanceData class which is responsible for loading balance data from our API. This class publishes a balance variable for our views to observe.

import SwiftUI
import Alamofire

public class BalanceData : ObservableObject {
    
    @Published var balance: Int = 0
    
    init(){
        updateBalance()
    }
    
    func updateBalance() {
        let token = NSUbiquitousKeyValueStore.default.string(forKey: "laravelToken")
        if(token != nil)
        {
            let headers: HTTPHeaders = [
                "Authorization": "Bearer " + token!
            ]
            AF.request("http://credits.test/api/auth/balance",method: .get, encoding:JSONEncoding.default, headers: headers).responseJSON { response in
                switch response.result {
                case .success(let JSON):
                    let response = JSON as! NSDictionary
                    DispatchQueue.main.async {
                        self.balance = response["balance"]! as! Int
                    }
                    
                    break
                case .failure:
                    break
                }
            }
        }
    }
}

My BalanceView file looks like this – nothing fancy, but shows the balance from the server!

import SwiftUI

struct BalanceView: View {
    @EnvironmentObject var balanceData: BalanceData
    
    var body: some View {

        VStack {
            Text(String(self.balanceData.balance))
                .font(.system(size: 72))
                .foregroundColor(.accentColor)
            Text("Credits")
                .font(.system(size: 18))
                .foregroundColor(.gray)
        }
    }
}

struct BalanceView_Previews: PreviewProvider {
    static var previews: some View {
        BalanceView()
        .environmentObject(BalanceData())
    }
}

Like I said, nothing special but there’s some useful tidbits of code there if you’re thinking of doing something similar.

I’m not a UI designer. Obviously

What’s next? Probably updating the users balance from the app.

Categories
Code iOS Laravel

iOS In-app purchases and Laravel

This is part two of a completely unknown number of linked articles as I’m writing them without even the slightest bit of aforethought. They’re not tutorials as such, but more documenting my progress and thinking as I forge onward building something.

It’s been a while since I’ve used IAP on iOS. This series of articles will follow my erratic thought process as I create the following:

  • A small web app, managing anonymous user accounts and their credit balance
  • An iOS application which allows you to top-up your account, restore purchases etc without the need to create an account.

This post will cover the authentication side of the app – I’m taking the new Laravel Sanctum functionality for a spin.

First thing I did was set up a new Laravel app, install Sanctum, and migrated all the things. I know I’m going to need an endpoint that accomplishes the following:

  • Handle a POST request with some JSON data
  • Check the request comes from a valid source
  • Creates an anonymous user
  • Issues a token for that user
  • Returns a JSON response with the user token

My function ended up looking like this:

public function token(Request $request){

    $validation = Validator::make($request->all(),[
        'key' => 'required',
    ]);

    if($validation->fails())
        return response()->json(["errors"=>$validation->messages()], 200);

    //TODO: Something a bit more robust than this; probably use it to check against an Apps table or something like that
    if($request->key != "supersecretappidentifier")
        abort(403);

    //Create anon user. You could use your own user model. I've stuck with the Eloquent one and faked the data, as I figure I may allow users to create an account at a later stage.
    $faker = Factory::create();
    try {

        $user = User::create([
            'name' => $faker->name,
            'email' => $faker->email,
            'password' => Hash::make($faker->password),
        ]);

        //Create the Sanctum token; again you'd probably use the name of the App or something
        $token = $user->createToken($request->key);

        return response()->json(["token"=>$token->plainTextToken], 200);
    } catch (\Exception $e)
    {
        return response()->json([
            "error"=>true,
            "message" => $e->getMessage()
        ], 400);
    }
}

Hit the endpoint with Postman and you’ll be given a shiny token to use in subsequent requests.

{"token":"C6UHgQXdrtCj7oEipaGCHU0Moq7uBGPpczEu1Lb1ApGpgMHSyfozQSwxvpRm524MdDbtrYBCNqU5s0Z5"}

Great! Now to make an app do it. Swift’s not my strong suit at the moment – I’m still writing most of my iOS code in Objective-C – so please bear with me.

We need functionality that does the following:

  • Checks if there’s a token on the device
  • If not, makes a POST request to our web service
  • Retrieve and store the token in the iCloud Key-value storage

I’m using iCloud storage for this example, but it would be a lot better and safer to use an iCloud based Keychain in a production app.

We save in iCloud so that if the user uses the app on another device – say their iPad – they’ll be linked to the same account at the web service end, whilst remaining anonymous in the eyes of my application. You’ll need to add the iCloud Key-value storage capability to your app to make this work.

The code looks a little like this. I’m using AlamoFire to handle network requests.

let token = NSUbiquitousKeyValueStore.default.string(forKey: "laravelToken")
if(token == nil)
{
    let params: [String:String] = [
        "key": "supersecretappidentifier"
    ]
    AF.request("http://credits.test/api/auth/token",method: .post, parameters: params, encoding:JSONEncoding.default).responseJSON { response in
        switch response.result {
            case .success(let JSON):
                let response = JSON as! NSDictionary
                let token = response.object(forKey: "token")!
                NSUbiquitousKeyValueStore.default.set(token, forKey: "laravelToken")
                NSUbiquitousKeyValueStore.default.synchronize()
                break
            case .failure:
                break
        }
    }
}
else
{
    print(token!)
}

The first time you run the app, it’ll make the request and store the token. Subsequent runs will print out the stored token.

That’s it for today I think – to recap, we’ve created a web service which creates an anonymous user and a token for the app to retrieve and store.

In the next article, I’ll add some credits functionality to the user and display our balance on the iOS device.

Categories
Code Thoughts

Side projects

Over the years I’ve worked on hundreds of side projects that have never seen the light of day. There’s a number of reasons for this; sure some are abandoned, but others are just prototypes, others find their ideas repurposed for client work. Others are on the later-base.

They’re always fun to work on, give you a different perspective and ultimately make you better at your craft.

I’m hoping to follow more of my own advice and get a few more of them shipped.

First one I’m going to look at is an iOS client for HobbyScribe. It has a modest user base and a modest set of features, but should provide a couple of useful and re-usable elements for more apps I’ve got lying around.

Don’t want to make it some sort of app a month challenge, but I would like to get a couple more out there if only to try things out.

Bear with me.

Categories
Code

WooCommerce: Don’t show variation attributes on the front-end

Recently imported many products, variations, attributes in to WooCommerce from a CSV file. All the attributes used for variations were set to show on the front-end – whoops!

Two options: Go through each product and uncheck “Visible on the product page” or this bit of code:

add_action('init',function(){
    $args = array(
        'post_type'      => 'product',
        'posts_per_page' => -1,
    );

    $products = get_posts($args);
    foreach ($products as $post) {
        $update = false;
        $meta = get_post_meta($post->ID,'_product_attributes',true);
        if($meta)
        {
            foreach($meta as $k => $a)
            {
                if($a['is_variation'] && $a['is_visible'])
                {
                    $a['is_visible'] = 0;
                    $update = true;
                    $meta[$k] = $a;
                }

            }

            if($update)
                update_post_meta( $post->ID,'_product_attributes', $meta);
        }
    }
});

Loops through products, and changes the meta as if the box were unchecked. Use at your own peril – definitely not on a production site!

Categories
Thoughts

Why I don’t hate Jar Jar Binks any more

I remember going to the cinema to see Star Wars Episode 1: The Phantom Menace. I don’t remember leaving the cinema hating George Lucas, Star Wars or Jar Jar. When the next two movies in the prequel trilogy were released, I diligently went to the cinema and watched them, enjoying them both.

Over the next few years though, society conditioned me to hate the prequels. Jar Jar was villified as the death of the much loved Star Wars franchise of old. People who loved Star Wars suddenly seemed *mean*. My own viewpoint was skewed – I’d rip on Phantom Menace just like everybody else at every oppurtunity.

The new trilogy has re-ignited the meanness. Only now, with the rise in social media it is more widespread, constant and even personal.

Why the hell am I ranting about Star Wars?

I’m not really. It’s not just Star Wars. There’s elitism and judgement everywhere. Have an opinion either-way on Brexit? You’re an idiot. Write your code using PHP? You’ll never be a real programmer. You’ve enjoyed Lord of the Rings without reading the books? Crikey.

I’ve got to a point in my life where I’m quite happy with my own opinion, and realise it doesn’t really matter what other people think. I’m still very opinionated, but I’m trying my best to not get dragged in to pointless arguments and letting people get on with what they like doing whilst I do whatever I like doing/watching/listening to. It is hard at times though.

People who spend all their time hating on Jar Jar and The Last Jedi obviously have too much spare time and ought to grow-up.

That said – still not a fan of Rogue One.

Categories
Thoughts

Number 9

2019 is a year of anniversaries. It’s 50 years since man landed on the moon. Primark is 50 years old. Gilwell Park celebrates its centenary. 50 years have gone by since The Beatles stepped out on the roof and played their final public performance. Friends; the sitcom about a group of mid-twenties in New York is now 25 years old.

Whilst most of these are somewhat important to me – none of them are quite as important as this one: Genius Division – the small agency I run with James and Craig – turned 9 years old today.

Although I didn’t join Genius Division until just before their 2nd birthday, I’d been collaborating with them on projects for a while and shared an office with them when I first went freelance in 2011. James mentions that they convinced me to quit my job at any opportunity – even in his best man’s speech at my wedding!

I was humbled when they asked me to become Technical Director in 2012 – it began an exciting new chapter of my life, and has remained pretty much the one constant throughout my early twenties, meeting my wife, becoming a homeowner, and having our first child.

Being part of Genius Division has allowed me to hone my skills and forced/allowed me to learn some new ones. Luckily we’ve had more ups than downs, and have always accepted the next challenge graciously – sometimes with cautious optimism, sometimes with a little reckless abandon.

We’ve never become complacent. We’ve evolved from tongue in cheek “Geniuses” – I still remember chuckling when James told me about the geniusdivision.com domain he’d bought – into a highly trusted, safe pair of hands for brands, businesses, charities and agencies alike.

We’ve always had quite a unique set of skills for a few lads from ‘tarn – we do everything in house; no outsourcing. We can design an engaging brand. Build you a SaaS solution. Make a website that works for your business. Launch a mobile app. Consult on technical issues, servers and cloud infrastructure for your organisation. It’s not always easy, but we’re problem solvers.

Happy 9th Birthday Genius Division. You won’t believe what happens next.

Categories
Thoughts

Ship it

How many times have you had an idea, worked on it for hours/days/months/years and never gotten the bloody thing finished?

I have loads of ideas. They might all be garbage, but you won’t really know until you release it to the world, or ship it. That’s when you get feedback. Nasty, dreaded feedback – but, you’ll also get helpful, constructive feedback and most importantly validation of your idea.

This’ll let you make tweaks, improvements, and in rare cases drop your idea completely. There’s no point spending those hours/days/months/years doing something no-one wants. Unless you want it I suppose.

Spend that time on the next idea. And don’t forget to ship it!

This topic has been covered by a lot of smarter people than me, but that sums up my feelings on it. I’ve been 50/50 about launching a personal blog for a long time, but never did it as I wasn’t sure if it was a good idea. Consider it shipped.