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!
This was just a quick update to fix the reordering of favourites issue which may have occurred. It should all be fixed now.
• Bug fix: reordering favourites would not save on all devices – this has now been resolved 💾 • Additional functionality: added in the option to reset the database in case anything breaks and it needs to be reset (this may need to be done if route functionality breaks – a long term solution will follow soon) 🗑️
The other feature added is one to reset the database in case things go wrong. This can be found right at the bottom of the list in the settings menu (tapping it doesn’t give much feedback at the moment, but it definitely is doing something).
If you notice that you cannot access bus route information any more or if you favourite stops have turned into numbers in the settings screen, tapping this should resolve it.
I’m working on making sure the app looks after itself!
There are two inaccuracies in my title: firstly, Flutter is Android native, but we will be talking about developing Android native apps using Java versus Flutter app in Dart; and secondly, one isn’t really better than the other, but in my circumstances, one approach was definitely better than the other.
A brief introduction
The important information about my background which you should know:
I published an app in 2013 and I think it has been moderately successful. Not a Candy Crush, but it hasn’t died yet and people are using it daily.
I rebuilt this app and it has feature parity with the old one – it does exactly the same thing the old one did (I know, because I’ve had users point out to me where I have forgotten how the “old” app worked). From their point of view, it just seems like I updated the look and feel a bit.
Development is a hobby rather than a job (although I work with development teams, but not as a developer). Java and Dart were both self-taught and never used in a professional environment for me. (I learned C++ at uni and have a long history with languages like BASIC, Pascal, C and Visual Basic).
So my qualification to make these statements come from building the same production app in both Java and Dart with limited experience. Let’s dive right into some comparisons:
Java vs Dart
Both languages are very similar and I feel fairly comfortable using both. I was a bit hesitant with learning Dart, but it felt completely natural after being quite used to Java (I’ve dabbled a bit with Python and PHP and I found it much easier to quickly adapt into Dart comparatively).
Doing asynchronous work in Java is a bit more of a hassle compared to Dart. Dart’s syntax makes it very easy (except that in Flutter, you build methods cannot use the asynchronous syntax).
I also got into Dart and Flutter when Dart 2.0 was out, so the language was cleaned up for Flutter, so no annoying “new”‘s everywhere.
And then, Dart either seems to handle nulls a bit better or I have burned myself enough in Java to be handling it better myself. But it feels like Flutter protects you a little bit as you don’t have a mini-explosion every time you forget to handle a null.
The only negative point for Dart, but this more a Flutter thing, is that you end up with lots of brackets everywhere – more than usual. And maybe quite a lot of commas too.
As a language, I prefer Dart as it is a bit more to the point and feels like it is made for what I want to do.
I spent a good number of weekends implementing material design at the start of 2015. It had only been announced in June the previous year, so it was still relatively new and there wasn’t too much help available in terms of libraries. I remember having to style everything by hand, measuring margins and constantly referencing the material design specs.
Flutter basically gives you material design out of the box. I actually hadn’t realised how “not material design” my app was until I rebuilt it using Flutter. (Of course, I don’t know if you now get some better help in Android native, but it felt really good that the design system I wanted to use was immediately available to me rather than having to sit and figure out margins, padding, shadows, rounding and so on, on my own.)
Getting a material design look in Flutter was effortless compared to doing the same thing in Java.
XML vs code
When you work with Java, a lot of layouts, strings and random values will reside in XML instead of code. This should make scaling with large teams easier as there is a separation of concerns and a designer can pick up the XML files and edit them without having to know code. As nice as this is, I don’t like it for a smaller project. And then, you end up having to reference the XML in code any way, and you always wonder why you can’t just do it all in code (maybe you can?). It felt like things got messy having to work in XML and then use LayoutInflater to build the things I just build in XML to start with.
On the flip-side, I miss one thing: Strings.xml. This allows for easy translation and just having everything nicely in one place. I have seen a way to do it in Flutter, but it didn’t immediately make sense to me. I never actually translated my app, so it’s probably over-engineered, but equally don’t feel too happy with the strings just sitting all over the place. (I should probably at least just create a static class to put all my strings in…but that’s for another day).
Because I definitely prefer being able to have complete control over my layouts, I prefer the Flutter approach where it is all in code. The XML approach would have been more useful for WYSIWYG editors, but they never worked too well for me and I didn’t feel completely in control. And the fact that it would try and display even the smallest layout in the editor. It was just odd.
The lack of WYSIWYG layout editor in Flutter is made up for by having hot reload. It can do some strange things at times (or in my app if I change things which are done right at start-up, hot reload doesn’t work), but it is great for making layout changes and being able to see it immediately. This is not available when using Java and slows down the process a bit.
Fragments vs widgets
I’m calling out fragments here, but it really should be, “Widgets vs the Android kitchen sink”. Everything in Flutter is a widget – this makes it very predictable on how it will work and easy to move around and plug in somewhere else. It took me minutes to take my existing layout for phones and adapt it for tablets where I wanted side-by-side views of two different screens.
Adapting my app for tablets in Java was a complete nightmare. Again, things might have changed, but I had to use fragments. I had two fragments and it just felt awkward trying to deal with it differently between tablets and mobiles. Because everything is a widget in Flutter – this sort of thing is quite straight-forward to achieve.
It’s worth pointing out, that even after all of that work with fragments, the app stopped working on tablets at some point and I just never had the courage to want to try and fix it again. Until Flutter came along…
I have three different flavours in both my set ups. The Flutter one would not have been as straight-forward to set up since I had to go and muck around in the Android code – but since I have done it before, it was pretty much copy and paste. If I was new to this, I can imagine it would have been pretty difficult to figure out as you have to change files you don’t normally deal with.
One of my flavours has a different applicationId which I can use in Flutter code to distinguish, but two of them don’t and I cannot easily and without some workarounds detect in code which flavour is which.
I would prefer it if there was an easier way to deal with flavours in Flutter, but it barely handles it at the moment.
For both Java and Dart, dependency management is quite straight-forward. Dart feels a little bit more light-weight and straight-forward – I have many more dependencies in my Java app for Android support libraries which I didn’t need in Dart (due to Flutter).
Due to not having to worry about the support libraries I prefer Flutter for dependency management, but I’m sure this might be why the APK’s are so big.
This was almost a deal breaker for me, and even though the production APK’s are quite a bit smaller than the debug ones, this is Flutter’s worst point for me by far.
The Java APK is 4.07MB and the Flutter one is 10.03MB.
Due to the large initial size of the Flutter APK (I started somewhere around 18MB), I had to be aggressive in terms of making images as small as possible, reducing the size of my static data database and be very careful with dependencies. Both APK’s are Proguarded/Minified. My Flutter APK is only that small due to a dedicated effort to try and get it as small as possible. If I put the same effort into the Java one, I’m sure I would get it to be a bit smaller.
Android Studio vs VS Code
I moved from Eclipse to Android Studio and liked it in the beginning, but it was quite heavy on the processing side. I’ve killed a laptop battery due to it and I’m fairly sure the heat Android Studio has generated in the past might have fried a few laptop components too. I now use a desktop, but Android Studio is still quite heavy-weight and it just feels too clunky.
Even though I use Android Studio from time to time due to wanting to use the layout inspector as an example, I prefer spending my time in VS code.
We have to touch on maturity here. Because Java Android has been around for years, you have access to tons of libraries and many resources. Getting stuck shouldn’t happen for long and you’ll be helped quite quickly. All the libraries have had years to stabilise and shouldn’t give you problems (well, you’d think so at least).
On the other hand, Flutter is still fairly new. I’ve had specific issues with libraries crashing my app because despite claiming to be version 1.1+, version 1 just meant it was their first publication. I’ve also had issues with libraries being incomplete. It’s worth pointing out that in both cases, I found suitable solid alternatives, so I haven’t been left with nothing.
This is probably set to get worse for now as many libraries are still experimental or beta. As these libraries are upgraded, I’m probably going to have to rewrite large chunks of code. (Although, my Java code is probably in that situation now as I haven’t upgraded libraries for about 6 months).
Neither app had a wearable version, but it was an option for me when I was working in Java. Flutter doesn’t support this yet, so if I had any plans to also have a wearable version, it isn’t currently possible.
I’ll mention it here only because it is also in the category of I don’t have it, but had the option and lost it – widgets. I believe that Flutter doesn’t yet support home screen widgets on Android, but this I cannot confirm.
Flutter’s best feature for me
So I’ve saved the best for last – Flutter gives me something I’ve wanted since 2013, but due to many issues, like time constraints, willingness and ability to learn, having to work on a Mac, I just never could build an iOS app. Flutter gave me this for free. Of course I had to set up a Mac in order to test it, but it just worked – pretty much out of the box with no code changes whatsoever (working is of course different to being usable – I’ve done a post on Flutter on iOS for Android developers about my lessons I’ve learned).
I’ve broken into a whole new market due to Flutter, so this is for me, the main reason that Flutter is better than using Java Android.
This is all my opinion based on my own use case, but hopefully you can find some useful bits in here if you are deciding whether to use Java for Android for Dart for Flutter. The checkboxes simply point out which is better at supporting the feature than the other – in most cases features are supported in both.
Dart & Flutter
Language – both languages are very similar, but Dart makes asynchronous development a bit easier and handles nulls better
Material design – I had to hand-craft material design when I worked in Java, it came out of the box with Flutter
XML strings – having Strings all in one place and an easy way to translate them was useful in Java. It’s possible in Dart and Flutter, but not without some hand-crafting.
XML layouts – I disliked having to manage my layouts separately in Java and prefer the control I get in Flutter.
Hot reload – only available in Flutter and turns your emulator into an almost WYSIWYG layout editor
Widgets – everything being a widget in Flutter simplifies things a lot and makes it clear and predictable how they will interact with other widgets
Flavours – this isn’t completely baked in, but supported by Flutter. I would prefer the ability to know programatticaly which flavour is being used and not have to fiddle around the Android source to set it up.
Dependency management – pubspec.yaml feels more lightweight than a build.gradle and I don’t have to worry about all the support libraries just to build an app
APK size – based on what you get for it, I feel that the Flutter APK is probably still too bloated
Android Studio and VS code. I prefer VS code as it is more light-weight and doesn’t try and cook my processor, although I switch to Android Studio from time-to-time for things like the layout inspector.
Maturity – it would be unfair to say that Flutter is incomplete, because it is possible to build a production-ready app with it, but some things will be a bit harder to do due to it still being relatively new.
Wearables – not supported in Flutter yet, but I expect it will happen if wearables are still popular in a couple of year’s time.
Ability to build for iOS – this is the winning point for me about Flutter. I no longer have to fear that I’ll have to learn Swift or Objective-C. It makes me sleep better at night.
This update was to bring back map views to the way in which they used to work, but also give you the ability to have new features (like a half-page view where you can also see the data in the bottom half).
I spent a little bit of time also trying to make file sizes smaller, make the app faster and use less data (it should already be pretty fast and using a small amount of data, but there’s always room for improvement).
Map views: you can switch between full screen and half screen. Some updates to make it work like the old version did. 🗺️
Update: updated wording and visuals for re-ordering of favourites in the settings page 🎛
Update: when re-opening the app, it should now refresh all of the data. 🔄
Bug fix: in rare circumstances, the app would crash on Android Pie (9) 🥧
Reduced the file size 📜
Speed improvements when loading details about routes 🏎️
Building a great app and making it a success on the app store, requires you to be much more than just a good developer. We often think that all you need is a good idea and some good development skills, but I can assure you, a successful app requires much more than that.
If you are considering publishing your own apps, here some of the things you might want to think about other than just the development aspect – together with good development, you need to also either learn these skills or source them from somewhere to make your app a success:
Market research: Are you building something that people will find useful? Are you just cloning something else which has already been done – in which case, what will make your app different?
Product management: A good product manager will tell you to focus your efforts on the most important things first and try to guide you to not waste time on things which are not important or not important immediately.
Testing: Do not underestimate testing. Especially as an independent developer, it is really difficult to test your own work, but it has to be done. There is nothing worse than a breaking bug making it all the way into production. You need to step away from the code and try and break your app.
User experience: When you develop an app, using it is quite easy, because you know how you made it. Have you considered what your app will look like to a user the first time they open it? I know my app exceptionally well and it always surprises me to watch other users use it.
Graphic design and UI design: This is something I struggle with a little bit, but I still give it a go (I do this so that I can get better at it). Publishing an app on the app store with an ugly icon or ugly user interface will immediately lose you points. As this is what a user sees immediately, I would even consider paying someone else to do it for me, but not before giving it a go myself first.
Marketing: Once your app works really well and you have published it, you are just waiting for people to download it. This really doesn’t happen by itself and you need to put effort in to get people to use your app.
Support: Things are going to break. You need the ability to look after your users and help them out. This can range wildly from code fixes, to explaining to them what they are doing wrong, updating your app to make it easier for users and so on.
Public relations: Once you are on the app store, you have gone public. Users are going to applaud you, they are going to criticise you, they are going to troll you. Especially when things go wrong, you need to be there for your users – this can be a tough one to do.
It is easy to think that all you need for a successful app is a good idea and some great development. These are definitely elements which will have a positive contribution, but there are so many other aspects you need to consider. It can be very daunting when you are the independent person and you have to do all these roles at once. But, in this, remember that you also have a competitive advantage – if users are not happy, or things change in the marketplace, you can change direction at the drop of a hat. You don’t have to ask someone’s permission or for through a bunch of bureaucracy to get things done.
As I continue writing, I’ll delve a little bit more into the various areas, specifically not focusing on development alone.
I was reading a post on Reddit which had some interesting statistics about Android OS versions and the speed (or tardiness really) of updating to the latest OS versions.
Of course the post is focused on individual phones, so it becomes difficult to get a sense of how many people are using the latest version of Android. So I decided, as I do, to check my own app for how version distributions have changed over the last four months. Here is a side by side comparison of the charts (the sample sizes are around 2500 users in both sets):
Of course – this doesn’t tell the full story either. It isn’t clear whether the shifts are due to software upgrades on the same phones, or whether users have just got new phones – or whether they are completely different users, but it paints a picture.
Something else to keep in mind when looking at this, is that my app is geographically biased towards London. So it isn’t representative of Android OS distribution across the world, or even Europe, it is very centred on London.
Here is the same data represented as a bar chart – just for a different perspective:
I’ve been looking around for interesting data points to share with other app publishers and found some data in a place I didn’t really expect to find it. After logging into my Google Analytics account for an entirely different purpose, I was taken to the analytics data for my app which I replaced with Firebase about 18 months ago. I don’t check it very often, but to my surprise, there is still a fair amount of action going on.
To give some context to the graph:
Version 2.1.1 – released November 2014
Version 3.1.3 – released 11 June 2017 and replaced on 24 June 2017 (Google Analytics was deprecated at this time).
This is amazing user retention for you! I don’t like the fact that I have data in two different places and that these users are mostly forgotten about, but I also cannot complain about it, since this is now just pure passive income. I actually cannot do anything to improve or remove it. Of course, it will also continually decrease as people upgrade phones, they uninstall the app and so on.
Why no upgrade?
At the point where I moved from Google Analytics to Firebase, I had to change my minimum SDK version from 14 to 15. This put me under the assumption that these users are probably unable to upgrade since they are using an old version of Android (4.0.2 or below). So I decide to validate it and check.
In fact, what’s not shown in the pie chart above (as you might think it’s hidden in “other”), is that I had 0 (yes, zero) users who were unable to upgrade to a later version.
Checking my Firebase stats confirmed a similar trend, some users are just very slow in upgrading their apps.
Apk’s lazing about on the internet?
I’ve found my APK’s all over the internet before and then had a look to see whether this is some new app store which I wasn’t aware of distributing old versions of my app. This pointed me to a really interesting installation source: com.sec.android.easyMover. Most of my users with old versions of the app, had this as their installation source. This is Samsung’s tool to move all your data from an old phone to a new one. Of course, this doesn’t point me to where these users initially got the application from, but it just points again at users who do not update their apps. (This also reminded me that I have a very small number of people using my app on the Amazon app store and I decided to upgrade it – it might actually work better for them, since I don’t use Google maps any more).
Architecting the enduring app
I did an earlier post on my app’s version history, where I walked through all the versions of my app from 2013 onwards. I was slightly surprised when I installed version 1.0.0 and it was still working exactly as it did the day I built it. This was mostly by design, but also with a bit of luck.
The external API which my application uses, hasn’t changed since 2013. This is the bit of luck which I had, because if the API (which is not in my control), changed even just a bit, all my old versions would break.
On the other hand, some of the design was deliberate. I had two options when I was designing my app – I could either stick a server between the mobile device and the external API or I could just call the API directly from the mobile device. (Incidently, I had already written much of the server-side code, as I had already done a web-version of the app before even starting the mobile version). I decided to cut out the server, mostly to keep costs down and also for speed considerations – I did not want to create a bottleneck or have to pay when it becomes really popular.
Note that just because option 1 worked out for me, it doesn’t mean that it is necessarily the best option (in fact, in many instances it probably won’t be). There are pros and cons to both and you have to carefully consider them. Speed and cost were two important considerations for me. Not having to have a server in between means that I don’t have to pay for it when 10,000 devices all try to access data at once.
On the other hand, if the external API had to introduce a breaking change, my old versions of the app would all break irreparably and all the users who have been happily using my app for years now, would be really upset (this could be really damaging for my app at some unknown point in the future).
I had to take a bit of a gamble in terms of how to architect my app – as an independent developer, I simply didn’t have the resources to maintain a server and I ended up here out of necessity. Also, as an independent developer, I don’t necessarily want to maintain a server indefinitely just to keep my app running.
There are no right or wrong answers on what the perfect architecture looks like, but you have to keep in mind that people may continue to use your app for a long time after you intended them to do so. One version doesn’t simply replace another and as soon as you publish something, it’s going to be out there for a long time, so make sure that you plan for this. You wouldn’t want you users to give you bad reviews in 5 years time because of a decision you made today.
As a quick side-note, I have seen apps which force you in the app to upgrade to a later version when it becomes available. I don’t like this – in fact, I hate it as a user experience. A better idea might be to just let the user know every now and again that they might be using an out of date version and they should consider upgrading. Don’t force it, unless it is absolutely necessary.