Categories
London Bus Pal

London Bus Pal v4.2.4

• Introduced dark mode which you can activate and deactivate in the settings. This should save your eyes and battery life! 👀

• Library updates to make sure that everything is as efficient and stable as it can possibly be. 📚

• Please don’t forget that you can support me by leaving a rating in the app store 🌟 or by removing ads for a small donation in the settings menu 💷

Categories
Development London Bus Pal

Dark mode: this is why I love Flutter

I’ve seen all of the hype around dark mode lately and I knew that at some point, I’d probably have to jump on that band wagon. But, it isn’t on my priority list as I’ve promised my users something else.

As I was wasting time yesterday, I saw a Youtube video about the dark mode craze and I thought, let’s give it a go – I needed a distraction anyway.

Flutter theme

  ThemeData theme = ThemeData(
      primarySwatch: Colors.red,
      primaryColorDark: Colors.red.shade700,
      secondaryHeaderColor: Colors.white,
      accentColor: Colors.orangeAccent,
    );

I previously pulled out my theme into a separate variable as I was playing around to make sure that my entire app respected the theme as far as possible, but my entire MaterialApp uses the above theme (it’s a bit redacted). In it’s basic form, all I had to do to change to dark mode, was to add the following into my theme:

      brightness: Brightness.dark,

And that was it. I had a look through my app and quickly found three or so instances where my app wasn’t respecting my theme. Generally, I pull the colours from the theme, rather than it being hard-coded, for example:

Color backgroundcolor = Theme.of(context).dialogBackgroundColor;

The worst offender was my search delegate and after a bit of playing around, I ended up with what feels like a slight workaround, it now does what it did before if the brightness is light (which actually doesn’t respect my theme too much, but it works and doesn’t change anything) and then it fully respects my theme if the brightness is dark:

  @override
  ThemeData appBarTheme(BuildContext context) {
    final ThemeData theme = Theme.of(context);

    switch (theme.brightness) {
      case Brightness.dark:
        return theme;
        break;
      case Brightness.light:
        return theme.copyWith(
          primaryColor: Colors.white,
          primaryIconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
          primaryColorBrightness: Brightness.light,
          primaryTextTheme: theme.textTheme,
        );
        break;
    }
  }

So that was it for being able to switch between dark mode and light mode in code, now to do it in run-time.

Switching during run-time

The implementation was quite straight-forward (I’m using the bloc pattern). Firstly, I hooked up a new setting in my application preferences (this doesn’t deserve a code example as it won’t be necessarily helpful for others), but what I end up with is a stream in my bloc which emits true or false depending on whether light or dark mode was selected. This is what the declaration in my bloc looks like to give you an idea (I start with false just to make sure that we have a value):

  BehaviorSubject<bool> _darkmodeController = BehaviorSubject<bool>(seedValue: false);
  Sink<bool> get inDarkMode => _darkmodeController.sink;
  Stream<bool> get outDarkMode => _darkmodeController.stream;

And then, the easiest part of all was hooking up the app (which I thought would be the most difficult) – btw I replaced irrelevant code with …:

class _BusPalState extends State<BusPal> with WidgetsBindingObserver {
  ...
  StreamSubscription themeListener;
  bool isDarkMode = false;

  @override
  void didChangeDependencies() {
    ...
    ApplicationPreferencesBloc _appPrefs =BlocProvider.of<ApplicationPreferencesBloc>(context);
    themeListener = _appPrefs.outDarkMode.listen((data) {
      setState(() {
        isDarkMode = data;
      });
    });

  @override
  void dispose() {
    ...
    themeListener.cancel();
    ...
  }

  @override
  Widget build(BuildContext context) {
  ThemeData theme = ThemeData(
      ...
      brightness: isDarkMode ? Brightness.dark :Brightness.light,
      ...
    );
    return MaterialApp(
      ...,
      theme: theme,
      ...
    );
}

And that is it. As I change from light to dark mode, Flutter takes care of the rest and even the transition looks really good.

I think I was worried as I don’t do too much setState, since I’m using bloc, but this just worked. It doesn’t break anything and it looks great.

This is why I love Flutter.

Here are some screenshots (in the interest of full disclosure, while I was looking some screens which would show light vs dark nicely, I found one other screen which doesn’t respect my theme – but it will be a quick and easy fix – test your apps properly!!).

Categories
Development

Monetisation – ideas vs implementation

About a month ago, I wrote a post about my ideas for improving the monetisation of my app (give it a read if you haven’t). Now, a month later, I’ve done the implementation and things have progressed and changed a bit since then.

Time limited benefits

After I wrote the post and before starting the implementation, I sat down and worked out all of the economics of what I was trying to achieve. Long story short, there is very little benefit in time-limiting some functionality. Even my smallest tip covers more than 2 years worth of advertising revenue per person. Therefore, I decided after doing these calculations, that I would simple turn off ads permanently for anyone who ever tipped me.

Subscription model

During the implementation, I found that there is a bit of work to be done to upgrade or downgrade someone’s subscription on Android if they’ve previously subscribed. This complication made me deprioritise the subscription in my initial implementation, but most of the code is there and it would probably not take more than 30 minutes to enable. For now though, the decision was made to have no subscription model.

The result

This screen should give a good idea of what I ended up with.

The financial result

It might be too early to tell, but the results so far are encouraging. The movement is slow, but it’s isn’t as slow as it was with my paid app. This is meant to replace my paid app, so I compare the result to that. So far, since I launched my in-app purchases, my revenue is double that of my best month of my paid app. It’s not a difficult record to beat, since it is less that my most expensive tip, but the word to use is encouraging!

Something else which is different from my planning is that I had assumed that most people would choose the small tip, a few would choose the medium one and I would probably never see the bigger ones being used. Almost the opposite has happened – in my small sample, I’ve had use of the two bigger tips, so again, the word to use is encouraging.

Feel free to reach out to me if you want to know more about what I’ve done. I’m always happy to share experiences.

Categories
London Bus Pal

Flutter in-app purchases (part 2)

My previous post (part 1) on implementing in-app purchases in my Flutter app, was a bit of a vent after being incredibly frustrated with Apple’s process for using in-app purchases. It went relatively well implementing it for Android, but I had some challenges with Apple.

Appears though, I have to eat some humble pie! Despite a strange approval process with Apple, it would seem like the purchases work without requiring their approval (at least they load up, not sure if I can actually buy something). I had incorrectly assumed that I wasn’t seeing them in my app, because they still required some approval. It turns out, I had to just write my code properly and it will all work.

I am not completely done yet with Apple, however, I believe I’m on the home stretch. I deleted my old, incorrectly named in-app purchases and replaced them with new ones which are named according to the suggested convention. I updated the code to work with the new names and submitted my app.

Yesterday, it was approved by Apple and I also had a courtesy note to say that the in-app purchases which I deleted, was approved at the same time.

Now the entire in-app purchase submission confuses me. I tried to submit the new ones with my build yesterday, but couldn’t really find them. Today, I thought I would check this again and try to submit a new build. The option is still not there, but I noticed the in-app purchases have changed status to “In review”. Apple’s “developer console” makes me feel very uneasy – it isn’t always clear how or where to do things and having a section like the in-app purchase submission only appear in certain circumstances is not very user-friendly.

I might just leave the app alone for a day and see if my purchases are approved. At least the app has been approved, so all good from that point of view.

When using libraries in Flutter, one of the main usability features for me, is that I don’t want to have different code for iOS and Android. Two days ago when I assumed that Apple was in the wrong, I was thinking that this library doesn’t really protect that developer experience. In hindsight though, it completely protects that because I managed to use the same code apart from my list of in-app purchase names for both platforms.

Now I just have to wait for the money to roll in!

The library in questioned which I used was flutter_inapp_purchase (version 0.8.8+2 at time of writing).

My first implementation of the support me screen.
Categories
London Bus Pal

London Bus Pal v4.2.3

This update was mostly to get the Apple version out – (version 4.2.1 was rejected by Apple). Version 4.2.2 and 4.2.3 are the same.

Also:

• Updated the app to give a warning if it takes too long to obtain location and to give you the option to force the use of Android location provider (if your phone struggles to get location). 🌐
• Made it optional to have a GPS on your phone to use the application. 📵

Categories
London Bus Pal

Flutter in-app purchases (part 1)

This is the first of a multi-part post on my experience with using Flutter in-app purchases (and in this case the flutter_inapp_purchase library – using version 0.8.8+2 at the time of writing to be exact).

Initial strategy

The idea around my in-app purchases was to provide my users with the ability to leave a tip. I did some rough calculations to determine some price points and settled on the following;

  • One time purchases: £0.99, £1.99, £4.99 and £9.99
  • Subscriptions: £0.69 per month & £4.49 per year

Of course, in my calculations I had to also keep in mind that these prices include VAT and also the respective app-store’s commission. Fees and taxes mean that I get to keep approximately 58% of these values.

Interestingly, the monthly subscription is almost similar in value to the £9.99 one-off purchase over the course of a year. It feels a bit cheaper.

Setting up

First off, I had to set up all of the purchases in the app stores – I started following this guide created by the library creator: Flutter In App Purchase on Medium. I set everything up in the Google App store first. It was a bit annoying, because the values you have to enter are the pre-tax values, so £0.99 becomes £0.83.

All my one-off tips are set up as consumable items, because I want to give people the ability to tip as many times as they want.

I actually also setup my Amazon too, but I don’t even know why. Both were relatively straight-forward so I figured Apple would be easy and proceeded to set it up without following the guide (mistake!).

Android setup

Coding it

Coding it was relatively straight-forward, pass a list of ID’s and get details of the in-app purchases. I then sort it in value order and display the details from the app store in my app.

While I was looking at how to handle subscriptions on Google Play, I decided to leave it alone for a while since it seemed slightly complex.

Testing it

I develop and test using Android first. Initially, I kept getting a PlatformException (responseCode: 3). This took me about an hour to figure out that in-app purchases don’t work on emulators. Switched to a real device and it worked quite well.

Since I wanted to make sure that I could easily change descriptions, text and prices of my items, I display all the data from the app store. Google adds in my app name into the description, so I have to strip this out again, otherwise it looks a bit ugly.

I also manage to figure out how to test with nearly real purchases. Google actually makes it quite easy to test. The only issue is, due to my implementation, if you’ve ever bought something successfully, I’ll automatically disable ads – this might become an issue in future, so I will have to figure out a way around this for my test devices. In the meantime, I decided to just test the “declined” or “cancelled” path so that I don’t disable ads forever on my test device.

The code route for disabling the ads is what I use in my pro version, so I was happy that it was safe to publish.

Publishing it

It all looked good on Android and I felt ready to publish. Apart from the emulator problem which was a bit of a hassle, it seemed relatively painless. I started the roll out and waited for the tips to roll in!

Republishing it

For whatever reason, I decided to test the purchase flow and approved purchase – just to double check. Argh! Yes, I use the same flow as I do for my pro version, but I don’t turn off the ads after being turned on in my pro version. After tipping, the ads are turned off, but the app left an ugly which space where the ad used to be – not what I wanted to happen!

Quickly fixed it and republished. And then waited…

First purchase

My first tip (which wasn’t the “test one” I did in production), came in a day later. It was a bit surreal – I actually felt bad for receiving a tip. That probably deserves an entirely separate post to explain. Either way, it seemed to be fine. I have money and it all worked fairly easily.

Apple setup

Android was straight-forward. I generally followed the guide and it was a bit easier than I thought it would be. I basically just copied what I had done for Android, but then it seems like I would be unable to test it.

When you first create in-app purchases for Apple, they go into “Waiting for review” and they will be approved when you submit your first binary which uses them.

First mistake

I setup all my in-app purchases to have the same ID’s across Android and iOS. This made the code a bit easier, but I think this might be wrong. I just used generic ID’s (smalltip, mediumtip, largetip, hugetip). All the examples for Apple suggest to use reverse domain names with the ID’s appended to the end, but these always appear as suggestions, just saying they have to be unique. Now I’m not clear, unique how? Unique across the entire app store or just unique for myself. I figured since I created it already, I’d just go with it.

Submitting in-app purchases for approval

So, I figured since my in-app purchases were not yet available, I could not test them until they were approved. My best chance to get them approved, would be to just submit my app.

My in-app purchases kept showing missing metadata. Apparently, you have to submit a screenshot showing the in-app purchases. This is difficult since they aren’t available yet. It felt like a chicken and egg situation, but I already had that figured out with Android, so I just took a screenshot of that and attached it to all my in-app purchases.

Half a day after submitting, I checked back and noticed that the in-app purchases were still not waiting for review. I went searching and saw that you have to submit the in-app purchases at the time that you submit your binary – it doesn’t just do it together.

Cancelled the submission, lost half a day and tried again, this time with all of the in-app purchases attached.

Failure

My submission was rejected. Together with a screenshot showing that no purchases are available (as my app does when it cannot find anything).

What next?

I am going to redo everything for Apple. I’m going to try and test it and see if anything will actually appear, but if it works like Android did, I’m going to have to do it blindly. I am just hoping that creating truly unique ID’s will sort everything out. Otherwise, I don’t know – maybe I’m going to have to go and buy myself a cheap iPhone to use for testing.

If this ever works, I’ll post a second part. Until then, assume I’m trying to figure out how to get it to work!

Categories
London Bus Pal

London Bus Pal v4.2.1

This release focused mainly around providing functionality to give me tips. It feels slightly selfish, so I also included some bug fixes.

  • Begging bowl – I added in functionality where users can give me money tips 🤑
  • Updated libraries to use the latest and greatest – this should usually result in more speed and stability 🚅
  • A number of small bug fixes. 🐛

(was version 4.2.0)