The post Software Developers + Time = Burnout (& How to avoid it!) appeared first on Rob J.
]]>I’ve seen developers on the same projects as me working overtime, evenings, weekends, answering non urgent phone calls at 8pm, sending Slack messages at 11pm — getting stressed and slowly working their way towards burnout, without any tangible benefit if they didn’t do all those things.
Meanwhile I’m working multiple contracts a year (sometimes simultaneously!) whilst intentionally avoiding burnout, producing the same amount and quality of work as my colleagues, with none of the added stress some of them endure.
So how do you avoid burnout when working remotely? Here’s my top 5 tips.
This is easily the number 1 thing to avoid burnout. Boundaries.
Firstly, there needs to be a cut off time after which, you’re not working. You’re not on Slack. You’re not answering phone calls. You’re just not working.
Now I know some people, especially those who’ve already formed this bad habit of always being available to their employer, will find this hard to do — so here’s a little hacks to make it a bit easier.
In Slack (or Teams if you have the misfortune to use that ) — set your Do Not Disturb to come on at the time you finish work. So for me, DND comes on at 5.30pm & goes off at 8am weekdays, while on the weekends, it’s always on. The beauty of this is when people go to message you, they’ll see a message that says you have DND on, and they’ll know whatever they send, you won’t see until tomorrow. They’ll also have the option to push a notification through and bypass DND, meaning that person will have to think to themselves is this message worth disturbing you outside of your working hours, so most people will generally only push the message through if it absolutely can’t wait until tomorrow. All the while, no one feels like they’re being ignored because they know, until you start work tomorrow, you won’t have even seen that message let alone have ignored it.
Secondly, don’t answer work calls on your personal phone. I’ve worked for people who will call at 7.45pm to tell you they’ve just found a bug, while testing your work, for the current sprint. Obviously not an urgent call. Now while my colleagues would answer these calls — I would not. In fact, I would never answer any call on my personal number from work because I feel like it sets a bad precedent. We have Google Meetup, Slack, Zoom, Teams etc for work calls, my personal phone number is not one of them.
Taking breaks away from your screen is something that was easy when we all worked in an office, people would go get coffee or have a chat with colleagues. Working from home oftentimes means getting coffee is a 2 min activity to the kitchen, and chatting with colleagues happens on the exact same screen where work happens — so we never really have a break.
My solution to this as a contractor is to take long sustained breaks between contracts, a month at a time usually, either to travel or to just take time off.
However if you’re not in a position to do that then simply take regular breaks from your laptop, whether it’s a 20 min break to watch some Netflix or it’s a 20 mins walk round the block (I advocate for the latter but have been known to do the former) — anything to get you away from “work” and give yourself a moment to relax.
Now this may seem counter intuitive, we are after all, working from home. However what I’ve found extremely useful when working from home to have a dedicated space where you work, and it shouldn’t be where you also relax.
The reason being it’s much easier to switch off from work when you’re not at work, and by having a space in your home which is dedicated to work, it makes it easy to switch off from work when you’re not in that space. Plus it also makes it much easier to get into work mode when you have a dedicated space for work.
If you have a home office, then you work there, and only there, and when you leave your office — you’re no longer at work. If you don’t have a home office, maybe pick the dining table, or the kitchen. Anywhere where you wouldn’t normally be using a laptop, so when your work day is over, you can close your laptop, put it away and be done for the day. In our previous home I worked at the dining table, which was in our living room, but the fact that I worked at the dining table meant when I was done for the day, and I went and sat on the sofa, I didn’t feel like I was at work because I never worked on the sofa. And no one’s going to mistake their plate of food for a laptop at dinner time!
The key thing is if you can separate your work space, from your living space, you’re much less likely to just check that email or just finish that piece of work.
You can relax.
Many times I’ve had the experience where as a team, we’re asked to estimate a piece of work, let’s say we decide it’ll take 5 days to complete. Then, when it comes round to doing the work, the project manager has decided we’re going to finish it in 3 days because someone has moved the goalpost to get it released, and the PM has promised it’ll be ready on time.
Now for a lot of the developers I work with, this is a stressful situation that’ll involve working evenings and maybe even weekends to get it done. Not for me.
Why? Let’s say you’re buying a house, and your offer of $100,000 is accepted. You go through all the lawyers and paperwork and on the day you’re about to transfer the money and take ownership of the house, the seller informs you they’ve decided they’re going to buy a bigger house than they originally planned, so you need to give them $150,000 for the house, instead of the originally agreed $100,000.
In this situation, I imagine most people’s response wouldn’t be to scramble to find an extra $50,000 to give to the seller. So why do most developers scramble to meet a deadline because someone in the company decided it needed to be done faster, after it was agreed it would take a specific amount of time?
Assuming you didn’t dramatically overestimate the initial work then there’s no reason for anyone to assume it can be done faster, and therefore it’s not going to be done faster without the detriment to your work/life balance and your mental health.
If you agree to a deadline, meet the deadline. If your higher up’s decide you need to work faster because they’ve moved the deadline — tough.
This is also something I see ALOT. Your manager is stressed because their manager is putting pressure on them to make you work faster because their manager has decided this 6 week project is going to be delivered in 4 weeks because their manager has pulled a 4 week release date out of their ass.
So now the developers are stressed because they’re taking on the pressure from the PM etc etc. It’s not a good place to be. However it’s pretty common, especially in startups, and the main thing here is — don’t take on someone else’s job pressure.
If you work hard, during reasonable work hours, and that means a deadline is still going to be missed. That is not your fault, or your problem.
You’re responsible for coding to the best of your abilities. You’re not responsible for the products overall success. Or whether your manager looks like a rockstar. Or their manager looks like a genius. Or the founder looks like the next Zuckerberg.
Be responsible for what you’re responsible for. Leave the rest.
The post Software Developers + Time = Burnout (& How to avoid it!) appeared first on Rob J.
]]>The post From Beginner to Junior Android Dev: 10 things you need to learn appeared first on Rob J.
]]>Now that’s not a slight against anyone, we all have to start somewhere.
But no matter how you’ve learnt to build Android app’s -whether you learnt on a coding boot camp or have just finished a degree, whether you’ve been following a Udemy course or have been following online tutorials..
If you’re truly serious about taking your Android programming skills to the next level. If you want to go from beginner to professional. If you want to go from hobby to career. Here’s a list of 10 things to learn that will put you light-year’s ahead of the competition.
And that’s not hyperbole either. I often get asked to help interview candidates and if your resume crossed my desk for a Junior position with the following skills — you’d be in (or maybe better put, if your resume crossed my desk without these skills..
Real talk.)
If you’ve any experience in programming you’ve probably heard this phrase before, if Android Development really is your first foray into programming then let me be the first to tell you this. Don’t reinvent the wheel.
9/10 times whatever it is you’re trying to do has been done by someone else. There’s either a library for it or a gist for it or a tutorial explaining how to go about it. So before you start rolling your own, have a Google, have a look on SO, and benefit from others mistakes.
Sure there are other networking libraries like Volley or Fast Android Networking, but if you’re looking to become a hire-able Android Developer then you need to know how to use Retrofit.
Learn how to do the basic’s like perform GET’s & POST’s, learn how to add headers to specific calls, learn how to hook it up to OkHttp for performance, learn how to create some basic interceptors, specifically you’d benefit for using one that let’s you log the request/response of calls verbosely (in the vain of not reinventing the wheel I’ll give you a hint, there’s an interceptor you can import that’s built just for this!).
JSON is the most widely used format of transferring data across API’s. You will come in to contact it working as an Android developer so you need to know how to work with it & convert it into your POJO’s (Plain Old Java Object’s).
The most commonly used libraries for this are Jackson, GSON & Moshi. You can use any that you like the look of, they’re all very similar & they all hook into Retrofit easily. You’ll wanna learn how to parse Json into your objects, how to annotate them correctly, how to override setters so you can control the way the data is parsed & you’ll probably also want to get a basic understanding of custom serializers & deserializers as they’ll almost definitely come to play at some point in your career.
I’d advice either Picasso or Glide, Glide being Google’s unofficially endorsed choice. They’re both very similar so knowing how to use one will let you get up to speed on the other pretty quick. You want to understand caching strategies, how to handle errors with fallback images & how to apply some transformations. A good example being how to take an image & make it circular to display as a profile picture.
There’s plenty of architectural patterns that you can use when building your Android apps. MVP, MVC, MVVM, etc etc.. My preference is MVP which is why I’ve highlighted it here, it’s also the pattern I’ve seen most commonly used when I start on a contract where the app is already in progress so already have it’s architecture predefined. For that reason I’d say learn this pattern & you can use the most commonly used Mosby library to do a lot of the work for you also.
If you’re really against MVP though or something else interests you more I’d say learn that. The main takeaway here should be while you may have learnt to build your app’s with everything in 1 activity or 1 fragment — no one build’s apps like that. Scratch that. Some people do. Let me rephrase — no one is going to hire you because you can do that, but they’ll hire you if you have worked with one of the more commonly used pattern they’re looking to use.
If you’ve just started learning & have been learning using some older Java tutorials then you need to get to grips with Kotlin. Kotlin is officially supported in Android Studio, it has numerous benefits over Java & it’s pretty much a prerequisite now when hiring Android developers. You don’t need to be a pro, and tbh the learning curve is not steep at all if you know Java. You just need to know enough to get started on a project immediately & learn the rest as you go.
If you’ve just started learning & have been learning using newer Kotlin tutorials, fantastic! You’re ready for the future. Unfortunately a lot of companies still live in the past so if you’re serious about becoming an employble Android developer then you need to be able to pick up their legacy code, meaning you need to know some Java. Again, if you’ve started with Kotlin then you just need to know the basic’s in Java to be able to get started. I would highly recommend you get somewhat competent though because if you can’t work with Java code then you’re pretty restricted in what Android developer roles you’d be able to take.
When I started programming I hated unit testing. In fact, every gig I’ve had in my 8 year career so far has shied away from testing in favor of pumping out code faster. Luckily I’ve not seen many major issues doing things that way however, a serious Android developer knows how to write a unit tester whether they’re required to or not.
Just know the basics. How do you set one up, how do you run it, how do you verify the result. You can later learn the more complicated things as those scenarios come up.
Following on from #7, one of the most useful things in the testing world is mocking. Imagine you have a Presenter you want to test & it has 6 other dependencies. You can go ahead & create all of those dependencies in your test code, maybe those dependencies have dependencies so you’ll have to create those too – suddenly your test class has more code in it setting up the test than it does executing the test. OR.. you can mock those dependencies.
Mockito & Mockk would be my two go to classes to implement mocking, with Mockk being my go to if you’re writing a Kotlin based test. Learn how to mock objects, mock results, spy objects — which is where the object behaves as normal but you can override methods to mock specific aspects of it. Mock’s also let you run code that might rely on an Android code inside a unit test as you can simply mock that object.
Espresso tests are essentially Android tests. You’re testing code in Android itself, either on a device or on an emulator. You can test headless code like services or you can run UI tests where, for example, you “click” on things & verify the right action is taken. You want to know how to write one, how to perform simple actions like click’s or scrolling & you want to know how to verify the results so your test can pass or fail.
There are very few scenario’s where you’d actually want to use findViewById. Personally I’m not longer a huge fan of Butterknife as if you’re writing Kotlin code it’s just not needed, one of the many benefits of Kotlin on Android. However if you’re serious about becoming a hireable Android dev than you need to know how to use Butterknife. Before Kotlin became more widely used Butterknife was pretty much included in every project to reduce boilerplate code so if you get a gig working on a Java codebase, 99% chance it’s using Butterknife.
It’s really not that hard, I’d steer away from learning things like the OnClick annotations as I find them to be bad practice when you’re working on a multi person code base (why are they always linted as unused!) & instead just learn how to Butterknife.bind & the BindView annotation & you’re good to go.
There you have it. My top 10 things you need to learn to go from being a beginner to having some serious Android game!
As always – question, feedback, thoughts.. all would be much appreciated in the comments below
The post From Beginner to Junior Android Dev: 10 things you need to learn appeared first on Rob J.
]]>The post Android Pro Tip: How to determine if you’re running in a Firebase Test Lab appeared first on Rob J.
]]>There are a number of reasons you might need to detect if you’re running inside a Firebase Test Lab, a few of them being:
You get the idea.
fun isInTestLab(contentResolver: ContentResolver): Boolean { return Settings.System.getString(contentResolver, "firebase.test.lab") == "true" }
Easy right? Firebase Test Lab returns true when querying for this system settings string, so you can now detect when you’re running inside the test lab inside your code & react accordingly. Simple.
If you have any thoughts or feedback, I’d love to hear what you think in the comments! Also if you have any requests for little hacks or tip’s like this you might be interested let me know & I’ll see what I can do!
I’d love to hear what you think!
The post Android Pro Tip: How to determine if you’re running in a Firebase Test Lab appeared first on Rob J.
]]>The post Android Pro Tip: Generating your app’s changelog from Git inside build.gradle appeared first on Rob J.
]]>To start with we’re going to start with one of the more boring but advantageous components of your app — the changelog.
I’ve always shipped changelog’s with my apps as they’re a nice way of keeping users updated of what’s changed in the app. Especially if it’s a bug fix they’ve been waiting for, or a feature that they might not realize has been added unless they’re explicitly informed. Hell I even built a library to reuse across all of my apps so I didn’t have to keep building the front end for it over & over again!
The annoying part of this setup however was always actually writing the changelog. Mostly because I’d forget and ship a build with an old changelog but also because when I actually remembered I’d find it pretty tedious to go back through my commits to remember what’s changed, so I could write the damn thing.
I recently discovered a pretty nifty way to automate this process entirely using the app’s own git log.
During a recent project there was a requirement to add a changelog to the app for internal test builds, so testers could see what changed in a build from one build to the next. However this particular project had multiple Android developer’s on it so wasn’t exactly feasible come release time to manually write the changelog without involving all the developer’s for their input on what’s changed & how we express that to the user. So instead, after a little investigation, I came up with this little solution that run’s from inside the build.gradle file – completely automating the process of generating the changelog, building it as a String in the BuildConfig file which I can then access statically from our code base & display directly to the user in our changelog UI.
Sounds easy enough right? Let’s look at the code.
To get started converting our git commits into changelog items, we firstly need to get the all the commits between now & the previous app release. If you’re tagging your builds when you release (and if you’re not why the hell not!) then this is super easy. Firstly you get the last release tag using the git command:
git describe --tags --abbrev=0
Next you can generate a list of commits since that tag, prettified into a format we can use, using the git command: (where $lastTag is the result of the previous command)
git log $lastTag..HEAD --oneline --no-merges --pretty=format:"%s"
The good bit about this being you can run both commands in your terminal, outside of Android Studio, to see exactly what is going to be output without having to keep re-syncing your Gradle file to run the task & see the result.
Once we’ve got that all we need to do is loop each line, format it in a way we want & append it to our changelog string — easy!
We also need to make sure we escape special characters as we’ll be outptting the result of this Gradle task into a String inside our BuildConfig file. As this is a Java class we’ll need to make sure any special characters are escaped so the final string produced is a compliant Java String.
The final Gradle task will look something like this.. (with println used to output useful information into our Gradle build output for debugging purposes)
String generateChangelog() { println "Generating changelog.."
//Get the last tag def lastTag = "git describe --tags --abbrev=0".execute().text.trim()
//Get all the commits since the last tag def gitLogCmd = "git log $lastTag..HEAD --oneline --no-merges --pretty=format:\"%s\"".execute().text.trim()
//Loop each line of the commits to build your changelog def changelog = "\"" gitLogCmd.eachLine { line ->
//Remove surrounding quotation marks generated by the git log comand def escapedLine = line.substring(1, line.length() - 1)
//Escape backslashes escapedLine = escapedLine.replaceAll(/(\\)/, "\\/")
//Escape quotation marks escapedLine = escapedLine.replaceAll('"', '\\\\"')
//Add each item to the changelog as a bullet point changelog += "• $escapedLine \\n" }
//Close the changelog string changelog = (changelog + "\"").trim()
//Useful log so you can see what was generated in the Gradle output println "Changelog generated, $changelog, from $lastTag to now."
return changelog }
Now that you can generate the changelog from inside your build.gradle file, you just need a way to make use of it. As I mentioned before, I wanted it to be statically accessible inside the app’s BuildConfig file so to do this, I added the following buildConfigField to the defaultConfig section inside of the app’s build.gradle file like so:
android { ... defaultConfig { ... buildConfigField "String","CHANGELOG","${generateGitChangelog()}" ... } ... }
Et voila! You end up with a single, bullet pointed Java string, containing every change since your last release, easily accessible by calling BuildConfig.CHANGELOG. Easy.
As not everyone’s requirements are the same- if, for example, you don’t want a single bulleted string but want each item separate so you can output it into a RecyclerView (like I do in my own app’s using the open source library I made specifically for this) you could use the same approach but use something like BuildConfig.CHANGELOG.split(“\n”), to create an array of your changelog items, split on the newline.
So there you have it. An auto generated changelog — always up to date, easily customizable, easily accessible.. easy!
If you have any thoughts or feedback on this approach, or know of a better way of doing this I’d love to hear what you think in the comments! Also if you have any requests for little hacks or tip’s like this you might be interested let me know & I’ll see what I can do!
Let me know what you think!
The post Android Pro Tip: Generating your app’s changelog from Git inside build.gradle appeared first on Rob J.
]]>The post How I got Android App Installs + IAP numbers mailed to my Inbox appeared first on Rob J.
]]>However, as an indie dev I still need to keep on top of things like installs, uninstalls, usage, IAP purchases & the like, mostly to identify any issues that might not necessarily trigger a Crashlytics email but could still substantially impact my user base. Unfortunately for me, that still requires more manual interaction then I want — I can live with the Play Developer console app but Firebase Analytics requires firing up a laptop, and sifting through the data across all my apps on the Firebase Dashboard — which definitely does not qualify as a digital detox of any stretch! Not to mention the WiFi in Asia can be pretty flakey at best & the Firebase console isn’t exactly light on resources..
Hence the reason I built a solution that would collate all the data I wanted to know about my apps, and send me a daily overview of it — straight to my inbox! Zero manual checks necessary, with a cost of $0 (or close to zero — we’ll see how the usage affects billing ).
Now if you just want to get all the analytics you care about sent straight to your inbox & have zero interest in how it happens or doing it yourself, you can skip the rest of this post & just sign up here. However for those of you who might want to roll your own or are just curious about how it all came together, let me explain how I did it..
The first thing when you’re building something like this is to figure out all the moving parts & how they all slot together. In this case I knew I’d need:
And it had to cost me $0 dollars. #poorindiedev
In the end I went with the combination of Firestore for storage, Firebase Functions to act as my API layer, MailGun as the email functionality for it’s easy to use SDK & great free pricing tier, and I nabbed a free email template from SendWithUs.
I also decided that to make this as simple as possible (there’s no easy way to get detailed analytics in an email & I wasn’t going to reinvent the wheel – if I want details I’ll have to suck it up & sign in to the dashboard), the SDK was going to be type & event based. Simply put you define a type ie “Feature Used” & you record an even for that type ie “Search performed”. The SDK will increment with every event & at the end of the day I’d know my search feature was used X amount of times today across Y installs. Simple.
Now that I knew my data structure I could go ahead & build out my Firebase Function to handle the collation & uploading of the data from my as-yet-to-be-built client side SDK. (I’m assuming you know how Firebase works but if not, here you go.)
Client side I’d be using Android’s SharedPreferences to record the data for it’s simplicity, I just needed a way to get it into the Firestore database without requiring the Firestore SDK. Mostly because I wanted to keep the SDK as lightweight as possible but also because I already use the Firestore SDK in a number of apps & I didn’t want the hassle of updating this SDK anytime they’d be a clash in version numbers. Also all we’d be doing is making 1 post request a day so a full SDK seemed kind of excessive.
Side note: If you’ve never used Firestore before, outside of creating some rules to protect your data there’s no real setup, you just start writing straight to the database.
Luckily Firebase functions provides you with the ability to expose any functions you write as a HTTP endpoint, which is exactly what I did:
exports.uploadStats = functions.https.onRequest((req, res) => {
return uploadStats(req, res) //This function is detailed next
});
To that endpoint we’d be sending a JSON payload in the format of:
{ some_type: { some_event: number_of_occurences }, some_other_type: { some_event: number_of_occurences }, ... }
And then compile that data with all the data collected from other users by first grabbing the package name of the app, which I’d be sending in the header:
var packageName = req.get(‘packageName’)
And then using that to grab any existing stats for the app already in Firestore:
var docRef = admin.firestore().doc("appStats/" + packageName) return docRef.get().then(snap => { var appStats = snap.data(); //If no stats exist yet create an empty map if(isNull(appStats)) appStats = {}
...
});
Next we’d loop all the type/events from our JSON payload to see if they already exist in the data we just pulled from Firestore and if so, we simply increment the count with the new user data, but if there’s types that don’t exist we add them:
//This function loops over the types Object.keys(inputStats).map(function(type) { var inputTypeMap = inputStats[type]; if(isNotNull(inputTypeMap) && isNotEmptyMap(inputTypeMap)) { var typeMap = appStats[type] if(isNull(typeMap)) typeMap = {} //Inner function loops of the events per type Object.keys(inputTypeMap).map(function(key) { var value = inputTypeMap[key]; var count = typeMap[key] if(isNull(count)) count = 0
count = count + value typeMap[key] = count
});
appStats[type] = typeMap } });
And finally we write the updated values back to Firestore.
return docRef.set(appStats)...
A total of 1 database read, 1 database write & 1 function run per install. Efficient if I do say so myself.
Now in the name of keeping things lightweight I also (much to your horror I know) didn’t want to include Retrofit as again — did I want to include a whole other library just for 1 Post call? So I did things the old school way.
internal fun uploadStats(context: Context) : Boolean { //The user has the option to disable all analytics/crashlytics from the ui if(!SdkUtils.getCollectionEnabled(context)) return false
val serverURL: String = BASE_API_URL + “/uploadStats” val url = URL(serverURL) val connection = url.openConnection() as HttpURLConnection connection.requestMethod = “POST”
//30 seconds because Triggers can be slow to start/return connection.connectTimeout = 300000 connection.connectTimeout = 300000
connection.doOutput = true
//This method simply loops through my shared preferences & puts them //into a map of type of: //Map<String(type), Map<String(key), Long(count)>>
val map = SdkUtils.buildMap(context) val json = JSONObject(map) val postData: ByteArray = json.toString().toByteArray(Charsets.UTF_8) connection.setRequestProperty(“Content-length”, postData.size.toString())
val outputStream = DataOutputStream(connection.outputStream) outputStream.write(postData) outputStream.flush() Log.d(TAG, “Response code: “ + connection.responseCode) if (connection.responseCode != HttpURLConnection.HTTP_OK) { val reader: BufferedReader = BufferedReader(InputStreamReader(connection.errorStream)) val output: String = reader.readLine() Log.d(TAG,”Api threw error $output”) return false } return true }
And I wrapped it in a kotlin coroutine for good measure. (I know, that is an extra library where I could just use an AsyncTask, but I’d just learnt it so it’s going in there – logic be damned!)
launch {
uploadStats(context) StatManager.clear(context) //Clears daily stats regardless of success
}
Now we just need a way to trigger the upload and the SDK is complete. For that I turned to the GCMNetworkManager replacement Firebase-JobDispatcher. Again this is a bit more weight to the SDK but it greatly improves the efficiency on the device compared to the alternatives so to me it was worth it.
val dispatcher = FirebaseJobDispatcher(GooglePlayDriver(context));
val myJob = dispatcher.newJobBuilder() .setService(UploadService::class.java) .setTag(UploadService::class.java.simpleName) .setRecurring(true) .setLifetime(Lifetime.FOREVER) .setTrigger(Trigger.executionWindow(windowStart, windowStart + toleranceInterval)) .setReplaceCurrent(true) .setRetryStrategy(RetryStrategy.DEFAULT_LINEAR) .setConstraints(Constraint.ON_ANY_NETWORK) .build();
dispatcher.mustSchedule(myJob);
The job will run in a 1 hour window every 23–24 hours from the time the app is installed, so once a day for efficiency. The timing means depending on the time zones stats will be uploaded at different times but as I just want a basic overview it doesn’t matter so much if some of yesterdays stats end up in today’s for some users. Plus the varied scheduling means 10’s of thousands of installs won’t all be trying to upload at the same time.
Oh and I used the on app updated receiver to set the job.
class AppUpdatedReceiver : BroadcastReceiver() {
@CallSuper override fun onReceive(context: Context, intent: Intent) { Log.d(TAG, “app updated..”) SubmissionManager.scheduleUpload(context)
...
Which, if you don’t know, is declared in the manifest with the intent:
<intent-filter>
<action android:name=”android.intent.action.MY_PACKAGE_REPLACED” />
</intent-filter>
And the SDK is complete!
Note: I purposefully omitted how I’m logging the actual stats as it’s simply incrementing a SharedPreference, but for reference I’m storing each SharedPref as a Long value with the name type:key separated by a colon, which I split at the time of upload to extra the type & key.
So right now we have an SDK to collect the stats & a way to upload them to our API layer which will do it’s thing & then add them to our Firestore database, meaning all that’s left is generating & sending the actual email.
First things first we need an email template, for which I turned to SendWithUs’s free open source email templates.
All I did was slightly modify the template I chose to work with HandlebarsJS so I could easily populate the email template directly with the JSON pulled from FireStore. Also, as the types & keys can be absolutely anything it needed a generic solution to display how I wanted. It looks something like this:
..header of the email..
{{#eachInMap this}} //Top level type
{{key}} //Create type title with key
{{#eachInMap value}} //Nest event:value for each type
Event: {{key}}
Count: {{value}}
{{/eachInMap}}
{{/eachInMap}}
..footer of the email..
Where eachInMap is a custom handler I s̶t̶o̶l̶e̶ borrowed from this Stackoverflow post.
var handlebars = require(‘handlebars’) handlebars.registerHelper( ‘eachInMap’, function ( map, block ) {
var out = ‘’; Object.keys( map ).map(function( prop ) { out += block.fn( {key: prop, value: map[ prop ]} ); }); return out;
});
With that done I created an endpoint in my Firebase Functions that could be hit to trigger the email. It takes a query string which is the packageName of an app, which it then uses to grab the data from Firestore.
exports.sendEmail = functions.https.onRequest((req, res) => {
var packageName = req.query.package_name var docRef = admin.firestore().doc(appStatsPath + packageName) return docRef.get()...
Checks if there’s any data to be emailed
if(isNull(appStats)) //If empty I throw a 400 response
If there is, it grabs out email template from the filesystem (if you keep it in the same folder as your index.js file it’ll get uploaded when you deploy), and builds the email using handlebars.
var fs = require(‘fs’); //Filesystem
var source = fs.readFileSync(“./email_template.html”,”utf-8");
const template = handlebars.compile(source, { strict: true });
var html = template(appStats);
And then we send the email with mailgun.
var data = { from: ‘[email protected]’, subject: ‘Your daily email overview!’, html: html, ‘h:Reply-To’: ‘[email protected]’, to: ‘[email protected]’ }
var mailgun = require(‘mailgun-js’)({apiKey : mailGunApiKey, domain : mailGunDomain})
return mailgun.messages().send(data, function (error, body) { console.log(body) send(res, 200, { message: ‘Success’, body, });
});
And we’re done!
Well, actually not quite. We still need a way to trigger this email daily so we don’t have to keep manually hitting out sendEmail endpoint. For this I went super high tech… IFTTT That’s right, I wrote me a quick recipe (using IFTTT’s webhooks plugin to hit the url):
IF ‘it’s midnight’ THEN ‘hit this url’.
Simple.
With that done, this morning, just like every other morning these days – I woke up to this lovely email!
No dashboards, no manual interaction, just an email, sent to my inbox, like clockwork! Nowww I can go off on my travels knowing that, should there be any major fluctuations in users daily use that might require some developer intervention, I’ll know about it without even having to look! (Plus I still get my Crashlytics emails so I’ll know about actual crashes also )
Now if you’re reading this thinking “this is cool, I want to do the same thing” but you don’t want to/don’t have the time to put in the work to replicate what I’ve done, you might be interested to know I’m considering opening up the service that I built to all devs. One easy to integrate SDK & you’d be all set!
If that’s something that does interest you can let me know by registering here and if enough people are into it — it’ll be rolling out as soon as I’m back!
And that’s that folks- thoughts, questions, suggestions.. any feedback whatsoever? I’m all ears..
The post How I got Android App Installs + IAP numbers mailed to my Inbox appeared first on Rob J.
]]>The post From the Dashboard to the Inbox — An #IndieDev’s analytics solution appeared first on Rob J.
]]>And then there’s my own apps. For which I’d love to collect specific analytics that help me better understand my users like, for example, which features of my app users like or use the most so I can focus my efforts there.
However, for every analytics SDK I’ve used in client apps, I’ve not found one that could tell me any of that without some over complicated setup involving filters, graphs, charts, keys, etc..
I mean, I do currently use the analytics dashboard, (shocker — I use Firebase Analytics) for some of the useful information it automatically collects, like making sure there’s no huge drop offs in installs because of some issue I wasn’t aware of, or on the off chance there’s a huge spike in installs as the app got featured somewhere prominent (hi Forbes ).
But that’s the extent of my Analytical Dashboard prowess.
Which is another thing — if I did figure out which correct combination of buttons & levers would get me to the data I want to know, am I seriously expected to visit the analytics dashboard on a daily basis to comb through all that for each one of my apps??
Dashboards I already use:
It’s ALOT of information. And I have to manually go in check all of it. Not to mention the fact that I find myself traveling a lot these days so I don’t always have access to good Internet or the time to go through all the data. All I really want is the cliff notes – the things I deem important, to be delivered to my inbox, once a day.. is that so much to ask?
Analytics made easy
Now before you get carried away, no — I don’t have a way to compile all that information into a single daily email. Although Google if you’re reading this — pretty please?
No actually where I’m going with this is, from that thought it occurred to me — what I really really want to know are things that are specific to my app’s usage. Not just generic things like downloads, or installs, or purchases, or even Activity views.. More specific.
Take one of my recent apps, Coffee-Working, for example. It’s a user populated app of coffee shops that are great to work from all around the world (or at least, where people have submitted places). And what’s important to me specifically for this app is..
Simple things, that require minimal effort to log, zero effort to compile & delivered straight to my inbox — no dashboards!!
And so, as us developers do when there’s no existing solution in sight (or our Googling powers for a solution have failed us) I built one.
What is DroidStats
DroidStats (such a creative name!) is a simple service that lets you log events in your app, however you chose. Activity opens, feature uses, etc.. Similar to how an analytics tool would work but much much simpler — it only counts events.
Each morning (which might end up being the evening depending where in the world you are — it’s a work in progress ) you get an email with an overview of all the stats you logged. This feature was used X times today. 25 coffee shops were submitted. 7 searches were performed for London while 3 where performed for New York. And so on.
It’s built on a typed event system so you can use it to log literally anything you want.
For example let’s say the type of event you’re logging is FEATURE_USE, the event itself is “Coffee shop submission”. Done.
StatManager.logEvent(context, FEATURE_USE, “Coffee shop submission”)
Every time someone submits a coffee shop you fire that event, once a day DroidStats will collate all the information submitted for your app across all your user devices & email you the daily overview.
It’s that simple.
There’s a few pre-provided Type’s in the SDK but you can create any type you like, likewise events are also completely custom. The SDK was built to only increment a count of whatever you tell it to, in fact the only thing it does by itself is count today’s app installs & today’s app updates to a particular version. And even then this is completely optional, if you have no interest in that or would prefer to check the Play Console yourself you can turn it off — personally I want to have to check it as infrequently as possible, hence why I built this in.
My Saviour — How do I get it??
Right now the service is in beta and being used in a number of apps to trial it (dw — my users have an option to turn if off per GDPR), so if you’re interested in giving it a go you can do so by registering your interest here, if there’s enough interest I plan on opening it up to everyone to use.
Now be forewarned all you indie devs, with your minimal budgets & your ingrained Android-ness of “it should be free” (I’m not judging — I’m one of you), I do plan on making it a paid service. I know — the horror! However my reasoning is not solely monetary. If we take just my user base alone, if every user was to have it enabled, that’d be around 100k writes per day to log each install’s analytics! Which equates to somewhere around a Gazillion dollars!!! Okay probably not a Gazillion, but it would cost dollars. And as much as I love the Android community there’s no way in hell I’m paying for you to use something I built. I might not be an analyst but I can just about do maths. Sorry.
However, I’d love to hear what people would think is a reasonable monthly fee to pay for something like this, to make it accessible to everyone. Assuming you want to use it — if not you can actually probably stop reading here, save yourself some extra eye movement.
If you are interested however, you can register your interest on the Firebase hosted, Jekyll built site I threw up here, you can also submit your vote on pricing there.
And that’s that! I would love to hear people’s experiences implementing useful analytics in their apps, am I missing the boat? Did I just reinvent the email for no reason? Is there an email service that already does this? Preferably for free? I’m all ears..
And of course feel free to leave a comment, privately message me if you’re shy & donate if you’re loaded.. you know the drill.
PS: In response to the inevitable question..
DroidStats does NOT collect any user identifiable information. In fact the only information it collects is the events you submit, along with a random UUID to identify that install purely so it can show you stats like “8 coffee shops submitted by 3 users”, where UUID is used to differentiate users.
Once the email is sent all of the data is wiped, both locally & remotely, and the day starts over. Meaning no stats are ever stored more than a day. Easy.
The post From the Dashboard to the Inbox — An #IndieDev’s analytics solution appeared first on Rob J.
]]>The post Why your App Developer Résumé sucks — and how to fix it appeared first on Rob J.
]]>It’s a good question. Sensible. But I don’t get it. Mobile developers of any stretch are in demand. If you’ve been at this a minute your phone is probably blowing up everyday with at least one unknown number from some recruiter on behalf of a client looking for your skill-set, your inbox is probably full of “are you looking for a change” emails from companies who want to pay you pennies to do the same job they’ll pay contractors millions. (Alright, yes, that pay scale is an exaggeration — but the wage structure of permanent vs contract really is massively out of wack!).
My response to said question always starts with “send me your résumé”.. and therein lies the rub.
Nobody cares about your interests.
Nobody cares about where you went to high school.
Nobody cares about the teamwork “experience” you gained working in Starbucks during college.
Nobody cares about how “talented” you are or how well you “work under pressure” or how your biggest weakness is your “perfectionism”.
No one is even reading your CV.
In fact, I’ve literally never had a call with a recruiter (if you’re looking for contract work, the industry is pretty much run by recruiters) who seemed like they’d even read my CV. It’s usually obvious because as soon as you answer the phone they start asking if you have skills that you’ve clearly outlined in your CV. In fact, most recruiters don’t even know what it is they’re asking you. They have no idea what Dagger is. They have no idea that when they ask you if you know Java, and then ask you if you have experience with “Object Orientated Programming”, they’re asking you a ridiculous question. What they know is keywords. What they’re looking for is keywords. That’s all people are doing, scanning your résumé for keywords..
RxJava – check
Kotlin – check
Retrofit2 – check
TDD – check
Knowing that, if you want to have a better chance at finding work, or better still, work finding you — your résumé needs to become a word diarrhoea of keywords linked to your experience, industry, work history & apps you’ve built.
And that last one is key — if you’ve built any app on the Play Store (or App Store) for yourself, list them on your CV. In fact, you don’t even have to list them.. “I have 3 apps on the Google Play Store”. Job done. (But actually have 3 apps of substance published — unfortunately 3 different versions of “Hello World” isn’t going to cut it)
Companies often look for people who have experience of building apps for themselves. Not only that but if you’re just starting out, you don’t have any “experience” so to speak, however if you have your own apps – you do.
This is the template I recommend to have the best chance of getting a call or an email. A foot in the door. Its worked for me & it seems to have worked for everyone I’ve recommended it to before. Plus, if you’re not having any luck atm — where’s the harm in trying this?
{ Name } { Phone number } { Title } (I advocate for [insert platform/language here] “ninja” but you can go with “guru” “master” or plain old “developer”.. whatever takes your fancy)
Experience { No more than 4 short bullet points outlining the most important experience you have for your industry ie — I have built & released 7 apps on the App Store — I’ve worked with 17 clients including Client A & Client B — I have extensive experience with [insert technology/sdk/language here] }
Work Experience { Client name, date, role} { A few bullet point outlining the most appealing thing you did for this client ie — Built this app from the ground up in 3 months — Project involved working with sdk a, technology b & language C etc }
(Repeat the last section for each project/client/app etc you’ve worked on)
Most people are unlikely to read past the first page but it’s good to get it all your work experience down, (the relevant ones – not your Saturday jobs) just in case the first page is missing some keyword the client is looking for but you happen to gained that experience 4 years ago and so it’s on the second page of your CV.
And that’s it. The goal always is to get the opportunity to present yourself for the role. Whether that be on the phone, via email or in person, and your résumé is the first step to getting that chance.
How you do from there is, I’m afraid, entirely up to you.
The post Why your App Developer Résumé sucks — and how to fix it appeared first on Rob J.
]]>The post Cease & Desist: Can using Android’s Notification Listener violate another apps T&C’s? appeared first on Rob J.
]]>Specifically relating to terms which state using the accusatory app in way that:
(At this point I should probably state upfront this post isn’t an admission nor a rejection of the claims made, I don’t have the legal knowledge to make either. It’s more a musing on the principality of such claims based on how the functionality in question was implemented & the overarching implications for indie developers if this is one more layer we have to muddle through in order for us to responsibly release our art)
On reading the perceived violations outlined above I was kind’ve pissed — how dare they limit my creativity & try to, in my perception at least, restrict what up until this point has been a very free flowing & open Android ecosystem. The more I thought about it though, the less emotional I got & the more intrigued I became with the implications this could have if they do indeed have legal standing to make this request of me.
My main qualm was due to the way in which I implemented the functionality of the app. It makes use of the Android NotificationListener to listen to all notifications. If that notification is from a package a user has pre selected as an app they wanted to reply to, and that notification implements a quick reply Android Intent, the app would use this intent to attempt to trigger a reply. Hell, I even wrote an entire post on how I implemented this using the official Android API’s & Objects.
The solution I implemented is entirely generic, it doesn’t target the app in question specifically, it works with any app that has made use of the quick reply (or Android Wear reply) Android API’s. It doesn’t interact with any app directly, it instead populates an Intent they provided in their notification to the Android system & fires it. What happens when it’s fired is beyond my app’s control.
Not to mention the user has to explicitly turn the Notification Listener on & is given a sufficient warning popup of it’s capabilities, so I wouldn’t find it likely it could be construed that the user is in any way unaware, ignorant potentially, but not unaware.
Before I continue I should note this wasn’t the exciting tale of legal cat & mouse, ending with a victory for the indie dev, that you may have hoped it would become.
The app in question has since been removed from the Play Store while I try to get my head around the implications. If I’m being completely honest it’s been hit & miss for a lot of users, it wasn’t widely in use & it never really worked in the reliable, useful way I had envisioned. It’s not so much the pulling of the app that bothered me as much as the principle.
The problem with something like this for an indie dev like myself is just that, I am an indie dev. I don’t have a lawyer on retainer. I don’t have the legal knowledge to know if this is a meritorious claim. I don’t have the money to hire legal counsel to dispute a claim I may believe to be wrong on principal, when the app in question makes zero ROI. I mean, I don’t even have an office, I’m writing this from a coffee shop. All I do have is a laptop, Android Studio, a little know how & a $25 Android developer account.
So I guess, trying to sound the least rant-y as possible (too late?), I have 2 questions:
1 — Is it possible that one app, using public Android API’s to interact with another app’s use of public Android API’s , can violate the terms of service of said app — on a platform they don’t own or control?
2 — Is it possible that an Android Notification, with it’s accompanying intent, sent from an application is therefore owned by that application & furthermore any interaction with that intent, whether it be parsing, inspecting, extracting nested objects and/or data etc, is in violation of that applications terms of service & may in fact constitute IP infringement?
And if that should be the case wouldn’t using the Notification Listener, or listening for certain broadcasted intents, in any capacity be potentially violating any app’s terms of service?
In fact, wouldn’t it be possible to write an app that inadvertently violates another app’s terms of service, due to the generic writing of code to interact with any Android Intent not sent by the Android System itself, regardless of its origin?
I guess what I’m really getting at is this..
Does any dev, working in this area of Android development, now need to start hard coding checks to make sure their app isn’t in any way interacting with an object, created by another app, for fear of violating terms of services & opening themselves to possible legal action?
I’d love to hear people’s thoughts, my thinking being if the answer to that question is in any way yes – I might just give up now.
This story is published in Noteworthy, where thousands come every day to learn about the people & ideas shaping the products we love.
Follow our publication to see more product & design stories featured by the Journal team.
The post Cease & Desist: Can using Android’s Notification Listener violate another apps T&C’s? appeared first on Rob J.
]]>The post The easiest way to get 5* reviews for your app? Just ask. (RatingManager Library) appeared first on Rob J.
]]>In my experience the majority of users aren’t inclined to leave a rating unless the app either a — blows their mind & they have to tell everyone or b — it absolutely blows & they need warn everyone “IT SUCKS!!”. Personally speaking all of my apps end up somewhere in the middle, a place where a user doesn’t even think to leave a rating — they use the app, it does what it said on the tin, job done. The only users of mine who will definitely leave a rating are those for whom the app is buggy & therefore they think it sucks (fair play), or users who think the review section of the Play Store is actually the feature request section (this happens wayyy to much).
So how do you get users to leave a positive rating for your apps when they’re really only inclined to rate your app when they have an issue? Simple — you ask.
Mind blowing I know but it really is that simple. Back in the day, where you’d get the email address of everyone who made a purchase of your app, I’d email every single person saying thank you & asking them if they like it to leave a review. Surprise surprise, they’d email me back that they’d never had a developer email them before, thank me, and then go on to leave a glowing review. Ahh the good old days. These days you obviously can’t do that, unless you’re doing something shady in your app to get that information.. in which case, please stop doing that.
No these days what you can do is ask for a rating in the app. I realise there’s a ton of rating manager libraries that let you do this, I’ve played with a lot of them, but my gripe with all of them (that I’ve tried — if I missed a killer one do let me know!) is twofold.
1 — They miss a crucial step.. Before I ask a user to leave a review I want to know if the user is going to leave a positive or a negative review. There’s no point prompting a user to leave a review only for them to go & give you a 1* “This app sucks” review. No what I want is to ask the user a — do they like the app, and if they say yes b — ask them if they can leave a rating. If they say no to liking the app then I want to be able to find out why so I can make the app a better experience for them & hopefully convert them to a positive review.
2 — They force me to handle the metrics I used to determine WHEN to show the prompt myself. Some libraries let you set how many days since install before it will prompt them to leave a rating, some have a few other similar metrics you can set but what I really want is to be able to set my own custom rules. For example, with my app ReadItToMe I only want to show a rating prompt to users who’ve had a certain amount of messages read to them. For Remindee maybe I only want to show the prompt to users who’ve set a reminder in the past month, otherwise when those users who haven’t used the app recently next open it, the first thing they’d see is a prompt asking for a rating. That just doesn’t feel like good UX to me.
So, in case the title of this post didn’t give it away, I built a library to handle all of that for me..
The first thing to note is this library provides 3 different prompts a user might see. The first one is the initialPopup which is where you ask “do you like this app” (obviously be a little more “human” with your copy ).
Then if the response to that is yes, the user will see a prompt asking if they’d like to leave a review to which they can respond “yes”, “not now”, “don’t ask me again”. (I should note all of the dialog text is fully customisable so the text displayed can be anything you like.) If the user responds “no” they don’t like the app there’s an optional dialog (you can toggle this feature) which offers them an option to email you to tell you why they don’t like the app. I’ve found this personally to be very useful in converting potentially negative reviews into positive ones by resolving whatever their issue was.
Firstly you need to create an instance of RatingManager. You do this by using the builder – RatingManager.Builder(context)
From that you can set your rules for when the prompt should show. The options provided are setMinDaysSinceInstall & setMinDaysSinceFeedback (the amount of days after they sent you a feedback email before the prompt displays again), setMinDaysSinceAskLater (the amount of days after they chose the “ask me later” option before the prompt displays again). That covers the basics. Then if you want to add your own metric for example maybe you don’t want the prompt to show until the user has played your game at least 50 times you can do add a custom rule such as:
new RatingManager.Builder(context) .setMinDaysSinceInstall(MIN_DAYS_SINCE_INSTALL) .setMinDaysSinceFeedback(MIN_DAYS_SINCE_FEEDBACK) .setMinDaysSinceAskLater(MIN_DAYS_SINCE_ASK_LATER) .showFeedbackOption(true, {feedback email address}) .addCustomRule(() -> DataManager.getGameCount() >= 50)
Furthermore you can completely customise the dialog displayed by using the RatingDialogOptions.Builder. An example being:
new RatingDialogOptions.Builder(context) .setDialogThemeResId(R.style.AppTheme_Dialog) .setInitialPopupMessage("Do you like this app?") .setInitialPopupPositiveBtnText("Yes") .setInitialPopupNegativeBtnText("No") .setInitialPopupLaterBtnText("Ask me later") .setRatingPopupTitle("Leave us a review!") .setRatingPopupMessage("You can help others discover this app by leaving us a 5* review") .setRatingPopupPositiveBtnText("Rate now") .setRatingPopupLaterBtnText("Maybe later") .setRatingPopupNeverBtnText("Don't ask me again") .setFeedbackPopupTitle("We're sorry to hear that") .setFeedbackPopupMessage("We'd love to know why so we can help improve the app for you & others") .setFeedbackPopupPositiveBtnText("Email us") .setFeedbackPopupLaterBtnText("Maybe later") .setFeedbackPopupNegativeBtnText("Not right now")
You can then apply this to the rating manager via setRatingDialogOptions.
Once you’ve built your rating manager you can simply call mRatingManager.showDialogIfRequired() from anywhere your app (I call it in the MainActivity’s onResume method) & if the rules are met it’ll show up, if not it won’t. It’s that simple.
You can find the library on GitHub here and you can add it to your project from Gradle via the JitPack repository (instructions on Jitpack if you don’t know how).
Feel free to use it, pick it apart, improve upon it.. And let me know what you think in the comments
A few things to note:
new RatingManagerMockBuilder(context) .setDaysSinceInstall(7) .setDaysSinceAskLater(13) .setDaysSinceFeedback(45) .setFeedbackBuild(BuildConfig.VERSION_CODE - 1) .setNeverAsk(false) .setRated(false) .build();
You can mock when the prompt was last shown, if a rating was already left, which build the user last emailed you with feedback about etc..
The post The easiest way to get 5* reviews for your app? Just ask. (RatingManager Library) appeared first on Rob J.
]]>The post Boilerplater Libraries: Device Utils appeared first on Rob J.
]]>Nothing overly fancy, just a few util classes that I use in numerous production apps that might just save you a bit of time (& potential headache). They’re also (for the most part) wrapped in RxJava2 observables so if you’re using Rx in your app (& if you’re not I’d highly recommend you should be) things like getting the currently connected Bluetooth device is now a ton easier to implement cleanly into your app.
They’re all very straightforward &sensibly named (at least to me ) so I’ll just give a quick overview over some of the most useful methods so you can determine if they might be of any use to you..
getInstalledApps(Context context) — Observable that returns a list of the currently installed apps on the device with the intent “android.intent.action.MAIN” & the category of android.intent.category.LAUNCHER.
This class also provides method to get a bitmap of the app icon for the provided package name (getAppIcon) as well as another straightforward method to get the app label for a particular package (getAppLabel).
This class provides numerous methods to query contacts based on their name, their contact id, their phone number, their uri (supplied via the extra People field in some chat apps), as well as methods to get a list of all the contacts on a users device (getContactsObservable).
As well as that it also provides methods to retrieve a contacts avatar & another to retrieve all phone numbers for the specific contact.
getSavedWifiNetworks — Returns a list of all the saved wifi networks on a users device.
getConnectedWifi — Returns the currently connected wifi network.
This class also contains numerous other method to retrieve the wifi name from an id & vica versa, pretty useful when a broadcast receiver for wifi only returns an id.
getPairedDevices — Returns a list of all the bluetooth devices paired to a device.
getDeviceAliasName — Easily retrieve the name of a bluetooth device based on it’s bluetooth address, pretty useful as it’ll return the custom name a user might have set for their bluetooth device if there is one i.e. “Kitchen Speaker”.
getConnectedBluetoothDevice — Returns the currently connected bluetooth device if there is one.
There’s a few other methods in each class not mentioned but they’re all pretty sensibly named so feel free to take a look & see if there’s anything useful to you
You can find the library on GitHub here and you can add it to your project from Gradle via the JitPack repository (instructions on Jitpack if you don’t know how).
Feel free to use it, pick it apart, improve upon it.. And let me know what you think in the comments
Side note: This library will be continuously updated as my apps are built upon & I find needs for other functionality but as it stands this is the library currently used in ReadItToMe, Can’t Talk & Remindee so I’m pretty confident in it for production use.
The post Boilerplater Libraries: Device Utils appeared first on Rob J.
]]>