02 November 2015

Annotating all (or most of) the things

If, like me, you are old and have been developing for Android for a while, you should, like me, appreciate the fact that the backwards compatibility of the OS has come a long way. Sure, they may toy with my feelings from time to time, but we all need a little excitement every now and then.

I have recently decided that I will invest more time into learning how all the tools at an Android developer's disposal can make me code better, faster, cleaner, and less buggy (I initially said "buggier" because I want to rhyme but someone who supposedly does English better complained).

To start with, I have been trying recently to consistently use the Resource Type annotations. These annotations prevent code like this from exploding:
private void setThingsToTextView(int res1, int res2, int res3, int res4) {
    // do stuff
}

This will explode because:
1. Fields are named horrendously
2. Without reading what the method does, it is so easy to pass the wrong resource ID (I can only assume that it wants resource IDs)

Resource annotations help with reason #2 by letting you and the compiler know just what type of resource is expected. There are a lot of available annotations (Read the docs!) but I find that the things I use the most are, well, the things I use the most:
@StringRes - expects an R.string.*
@DrawableRes - expects an R.drawable.*
@IdRes - expects an R.id.*
@ColorRes - expects an R.color.*

I have updated my SDK Sandbox project with an Activity to illustrate use of these annotations. FAIR WARNING: IT USES ENUMS. If this annoys you, DO NOT click through.

So how do we stop the method above from exploding? Let's fix all the things!
private void setThingsToTextView(@IdRes int textView, @StringRes int introText, @DrawableRes int heroImage, @ColorRes int backgroundColour) {
    // do stuff
}

Ahhh. Easy. And so much better.


23 September 2015

NOT another day at the office

We had another round of Innovation Day at Domain last month, and I wrote about it. We started out dreaming up this ambitious project -- too ambitious for two days! Here's a partial list of what we had to do:
  • Build a wall
  • Stick devices on said wall
  • Make app that cycles through photos from listings
  • Load said app on those devices that we stuck to the wall
  • Figure out how to track people who get devices
  • What if someone just gets a device?!
  • Figure out how to let people give back devices
  • Oh! oh! oh! Wouldn't it be cool if other devices cheer when one of them "comes home"?
  • How do we put new versions of the app on those devices?
  • Run tests, maybe?
  • What if the website team wants to test responsive designs?
  • Do they even charge????!!!
It was a LOT of work. But it was awesome.

15 September 2015

In Which Things Got Cheesy

Today, Android Developers published Domain's Developer Story. In it, Gary and Rique talked about how the Domain Android app was rated very poorly and had all sorts of problems. Fast forward two years and it is now a highly-rated, top-ranked lifestyle app in Australia. You would think that going from a 2.8 star rating to 4.1 stars is all sorts of amazing. And it is!

Rique mentioned how 2014 was a really big year for Domain; in a very personal sense, it was for me too. This video coming out gave me pause and kicked off a bit of a melancholy spell for me.

Around the middle of last year, I packed up half of my clothes, left all of my books and games, said goodbye to my family and my friends and my love, and moved to Australia. The choice to relocate was hard, and I almost didn't take it. I was just about to accept a new job at a big OEM, my boyfriend and I just bought an apartment, my friends and I have regular game nights -- everything was going really well. I really had no plans of going anywhere, I have long given up on the overseas dream. And then BAM, the Universe decides to spring this surprise onto me; and out of nowhere came this chance to work on something I truly enjoy doing.

I am so lucky and grateful to have been a part Domain's journey over the past year. The app did a lot of growing up, so did I.
I have possibly learned SO MUCH over the past year than the three years before that combined. Sure, it can get lonely being alone in a foreign country. Of course I miss my mom. Of course I miss my boyfriend. I surely do miss my friends -- I can never get anyone to play board games with me here. But at the same time, I have been meeting a lot of really great, really awesome people. I have learned how to cook (it turns out I can make a great cranberry feta salad, which technically is not cooking, but whatever). I have been on a lot of adventures, traveled to cool new places, jumped out of a plane, drank a lot of beer.
Plus, I DO have the best team ever. :)

10 September 2015

Raising Activities From the Dead

One of the scenarios I admittedly almost always forget to test is "What happens when my app goes into the background, then the OS kills is to claim memory, then I try to resume?" Usually it's "Well, I handle onSavedInstanceState not being null, so I am great!" It is fine and dandy for simple apps; but once your Activity or Fragment gets beefier and you start relying on state for more and more things, it can get complicated pretty quickly (In my case, the Fragment has setRetainInstance(true)).

This scenario in particular is kind of hard to reproduce willingly. I usually see this when I leave an app running, make my phone do some heavy work overnight, then resume the app the next day.

So what you gonna do?

It turns out that Android Studio has the answer! There is this magical tiny red button that allows you to simulate this exact scenario.

1. Open your app to the Activity you want to test (I use a very simple app here just for demo).



2. In Studio, go to Android Monitor (make sure that your app is selected). Note the process ID, in this case it is 25647.

3. Push your app to the background. Pressing the HOME button should be sufficient. This will call onSaveInstanceState, which is all that matters really. It is after all what we want to test.

4. Back in Studio, press the magical tiny red button pointed to in the previous image. Notice that Studio now appends [DEAD] to your app's process. It is now gone. He's dead, Jim!

5. Resume your app. I usually just do this via recent apps.

6. If you look at Studio, you'll see that your app is now no longer dead, but has a new process ID, in this case 26742.

If at this point you step through your code, you will notice that your Activity will go through the whole (re-)creation process with the Bundle given the values you have saved in onSaveInstanceState. No more waiting overnight, yay!

12 August 2015

Lies I've been told today

So I played around with data binding today. And these are the lies that the dev guide told me (explicitly or inferred):

  • There is a method DataBindingUtil.bindTo(viewRoot, layoutId)
  • That this will work MyLayoutBinding.bind(viewRoot);
  • Android Studio has auto-complete

13 July 2015

Pasting and Extracting Stuff

A lot of times, but especially when I am implementing some new logic, coding for me takes several steps:
1. Write down what I have to do as comments
2. Implement what I have written down
3. Refactor and improve what I have implemented

More often than not, step 3 means moving stuff around, copy-pasting things, extracting variables, defining constants, etc. I am not a hardcode never-using-my-mouse developer. If anything, I think my brain is limited to holding a limited number of shortcuts for everything I use in my life. Android Studio has the perfect shortcuts for making this easier for me. Luckily, these shortcuts made it to the list of things my brain remembers.

How many times have I copied (or even cut!) text but instead of pasting, I press ⌘+V (CMD+V) again! ARGH. I used to do ⌘+Z (CMD+Z) any number of times until I get back what I wanted. That is, until I learned about ⌘+⇧+V (CMD+SHIFT+V)! This key combo shows the clipboard history, which means no more fretting. Yay!

Refactoring also mostly involves extracting variables. I have already shown how to extract strings into strings.xml, and here I show how to extract things into methods, variables, fields, or constants.

It is fairly easy to remember them. Just combine ⌘+⌥ (CMD+OPTION) with the first letter of what you want to extract to. Time for a handy table!

Shortcut
⌘+⌥+M Method
⌘+⌥+V Variable
⌘+⌥+F Field
⌘+⌥+C Constant

This video might do a better job of showing what I'm trying to say. Code from Chris Banes's Cheesesquare demo.

08 July 2015

Super lightning talk: Tinkering with Tools

If you are just starting Android development or migrating from Eclipse to Android Studio, I gave a lightning talk on setting up some tools: 

14 May 2015

Stringy strings


While we are on the subject of strings, here are more ways of dealing with them in Android Studio. We all know that we should not hardcode strings in code, right? But sometimes, we forget and tend to do code first before defining them in strings.xml.

There are a couple of ways that Android Studio/IntelliJ makes this easy for us. The gif below (which took me a while to figure out how to do, by the way), shows how to deal with:
1. Moving a hardcoded string into strings.xml
2. Giving a previously undefined string ID a value in strings.xml

(Click to embiggen)

Move hardcoded string into XML:
This is probably the more common scenario. You happily put in texts into your TextViews, and now you have to copy and paste them into strings.xml. Don't! There is a shortcut for that.

Put your cursor somewhere in the string, press ALT+Enter to bring up the context menu, choose "Extract string resource" and give your string resource a name. This will create a new <string name="my_string_name">My string value</string> in strings.xml.

Studio magically also calls getString(R.string.xxxxx) for you. Neat, huh?


Make new string from ID:
This is for when you suddenly remember that you need a new string and want to sort of try to do it correctly so you type in the string ID. Only it hasn't been defined yet, so Studio complains. But it's fine. There is a shortcut for that.

Put your cursor somewhere in the as of yet undefined ID, press ALT+Enter to bring up the context menu, choose "Create string value for resource my_string_id", and type in the actual string value you want. Again, this will create a new <string name="my_string_id">My other string value</string> in strings.xml.

Remember, in both of these cases, Studio will create the new strings in strings.xml, but you can modify it to put in whatever variant you want it to be in (for localisations, screen size support, etc).

22 April 2015

Fixing a mistake in your git history

I have been using git for about five years now, but I definitely get stumped by it a lot. It is so powerful it's daunting. There has been a couple lot of times where I had been too careless and reliant on my fingers' add-commit-push muscle memory that I realised I have made a mistake too late. I have always been a proponent of clean, atomic commits, and when I find my commits all messed up, I hit myself in the head.

So, to stop myself from pulling my hair out looking for all the right StackOverflow answers that I KNOW I'VE SEEN THE SOLUTION BEFORE WHERE THE HELL IS IT???, I am writing the steps down to remind myself.

SCENARIO:
- I have committed some files in a previous commit that should not be there
- I want to REMOVE those files from that commit
- I want to preserve all other commits after that bad commit

CAVEATS:
- I am working in a local branch
- I will be removing those files forever
- I AM WORKING IN A LOCAL BRANCH*

SOLUTION:
1. Find out which commit you want to go back to.
$ git log

This should give you something like:
zarah.dominguez@R5003334 swipe-to-refresh-demo (master) $ git log
commit a6c00638b3d466a61e3381a98e6b44cf2d085164
Author: Zarah Dominguez
Date:   Tue Jun 17 16:34:50 2014 +0800

    Removed dependency on ButterKnife.

commit e25c6862a79270921a24d6bf2a9eb07cc3f03b36
Author: Zarah Dominguez
Date:   Tue Jun 17 16:07:33 2014 +0800

    First commit

If you just want the commit messages:
$ git log --oneline


2. Create a new branch based on the bad commit.
$ git checkout -b fix-that-shit e25c686

What this does is create a new branch named fix-that-shit, whose HEAD points to commit e25c686. The -b switch tells git to go to that newly-created branch.

3. Do your thing. In this case, I want to remove files.
$ git rm BadFile.java
rm 'BadFile.java'
$ git rm AnotherBadFile.java
rm 'AnotherBadFile.java'

4. Let git know that you've overcome your stupidity and are now saying sorry.
$ git commit --amend

An editor will open, and here you can edit the commit message.

5. Go back to the original branch. In my case, it is master.
$ git checkout master

6. Give this branch your changes.
$ git rebase fix-that-shit

7. Check your log. git might try and do it's thing, and do funny merges. So you might end up with a new commit in your history, something like:
zarah.dominguez@R5003334 swipe-to-refresh-demo (master) $ git log
commit 3e08701339c35301caf269058eab6359c9d87ecd
Author: Zarah Dominguez
Date:   Tue Jun 17 16:34:50 2014 +0800

    Removed dependency on ButterKnife.

commit ca57ac7974d7a422a2225612404cb6bd555acfc4
Author: Zarah Dominguez 
Date:   Tue Jun 17 16:07:33 2014 +0800

    First commit

commit 679ad41bedb2d61fbea36e39e334074b7de66dcd
Author: Zarah Dominguez
Date:   Tue Jun 17 16:07:33 2014 +0800

    First commit


7b. Examine the two commits, and you'll notice that the second in the list contains the file we just removed. I want to chuck that out completely, so I will do a rebase. This will take me back to the first commit in interactive mode:
$ git rebase -i --root

Now I can trash that bad commit by adding a pound sign (or fine, hashtag) before that commit's SHA:
pick 679ad41 First commit
#pick ca57ac7 First commit
pick 3e08701 Removed dependency on ButterKnife.

8. Check your logs again, and verify that the file is now nowhere to be found.
zarah.dominguez@R5003334 swipe-to-refresh-demo (master) $ git log
commit a6c00638b3d466a61e3381a98e6b44cf2d085164
Author: Zarah Dominguez
Date:   Tue Jun 17 16:34:50 2014 +0800

    Removed dependency on ButterKnife.

commit e25c6862a79270921a24d6bf2a9eb07cc3f03b36
Author: Zarah Dominguez
Date:   Tue Jun 17 16:07:33 2014 +0800

    First commit


9. Now kill and bury that shit:
$ git branch -d fix-that-shit

10. You will then need to force-push your changes if you have a remote branch. Hence the DO NOT DO UNLESS YOU ARE THE ONLY ONE USING THE BRANCH.
$ git push master --force



I am sure there is a more concise way to do this, but doing the verbose solution here for posterity.


-----------
* I cannot stress this enough. DO NOT DO THIS IF YOU ARE SHARING YOUR BRANCH WITH SOMEBODY ELSE. If you do, you automatically give them license to punch you in the face. (It can be a remote branch, as long as YOU ARE NOT SHARING IT WITH SOMEBODY ELSE)

20 February 2015

On being material

In case you missed it, I made a blog post about updating our app to material design. In it I talk about what material design is and what we did to adopt it. I hope you enjoy reading it as much as I did writing it. :) Head on over to Domain's tech blog for the details.

21 January 2015

SQLiteAssetHelper + ORMLite

I recently had cause to use Jeff Gilfelt's SQLite Asset Helper library. For those unfamiliar, it is a library that can help with including a pre-populated SQLite database with your Android application. It is extremely convenient with unbundling a potentially huge database you would want to ship.

For the app I was working on, I also wanted to use ORMLite. This is another library that helps with persisting POJOs to SQLite databases. If you deal with a lot of persisted objects in your code, then this is probably something worth looking into.

I won't deal here with how ORMLite does its stuff, I'll leave it to you to go through the documentation. What I'll write about today is how to make these two libraries work together.

Right. Moving on. If you look at ORMLite's sample code, it mentions that your database helper class must extend OrmLiteSqliteOpenHelper. If you look at SQLite Asset Helper's sample code, it mentions that your database helper class must extend SQLiteAssetHelper. What this means for us is that somehow, we need our database helper class to be able to talk to both of these libraries.

Since Android Studio is now the official IDE of choice, the sample code for this post is now AS-compatible. Yay!

First, gradle:
compile 'com.readystatesoftware.sqliteasset:sqliteassethelper:2.0.1'
compile 'com.j256.ormlite:ormlite-android:4.48'

When dealing with anything database, I tend to create my POJOs first. For this sample, I will be using the infamous Northwind database. For simplicity, this app will simply get the first entry from the Employees table and dump the contents into a TextView.

Since SQLite Asset Helper is the one who is in charge of our database creation and upgrade operations, we have to let it do the setup. Follow Jeff's example for that, BUT follow ORMLite's example for setting up ORMLite with no helper. At the end of it, you should have something similar to this.

To actually use ORMLite, just do as you usually would:
mOrmDbHelper = getHelper();

try {

    Dao<Employee, Integer> employeeDao = mOrmDbHelper.getEmployeeDao();

    // Try to get the first entry in the table
    Employee employee = employeeDao.queryBuilder().queryForFirst();
    if(employee == null) {
        textView.setText("No employees found!");
    } else {
        textView.setText(employee.toString());
    }
} catch (SQLException e) {
    e.printStackTrace();
}

Here we just get the first entry we can from the table and dump. If all goes well when we run the app, we should see this in the logs:

01-20 23:02:39.203  12555-12555/droidista.blogspot.com.ormsqliteassethelper W/SQLiteAssetHelper﹕ copying database from assets...
01-20 23:02:39.226  12555-12555/droidista.blogspot.com.ormsqliteassethelper W/SQLiteAssetHelper﹕ database copy complete
01-20 23:02:39.289  12555-12555/droidista.blogspot.com.ormsqliteassethelper I/SQLiteAssetHelper﹕ successfully opened database northwind.db

And we should see Nancy's details displayed on screen in all it's raw .toString() glory.