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.
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:
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:
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 …:
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!!).
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.
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.
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.
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.
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.
• 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. 📵
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).
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.
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!).
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.
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.
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!
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…
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.
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.
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.
My submission was rejected. Together with a screenshot showing that no purchases are available (as my app does when it cannot find anything).
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!