Showing posts with label Android. Show all posts
Showing posts with label Android. Show all posts

19 December 2022

Android docker compose image with Gitlab for CI/CD

This was a right pain and I do love to blog about things that cause me trouble and strife! I created a new Gitlab project and noticed that you can create from template. How exciting! So I selected Android and was delighted to spot a docker file and a yml file had been generated for CI/CD so it integrates nicely with Gitlab pipelines. Amazing! I can run my unit tests and do a build on every commit, this is fantastic.

Gitlab pipelines are a great little tool that builds and runs your project and can be configured as needed to do all sorts of clever things. What's more there's no hardware config involved, it uses docker so you just commit the docker image and let some hardware somewhere in the world build it for you. So simple.

Naturally this builds nicely from the template but the first thing I did with this file was upgrade the compile and target versions in Android's gradle file to the latest version so my app would be compatible with the Google Play Store.

Fail!

Warning: License for package Android SDK Platform 33 not accepted.

FAILURE: Build failed with an exception.

What went wrong:

A problem occurred configuring project ':app'.

Failed to install the following Android SDK packages as some licences have not been accepted.

platforms;android-33 Android SDK Platform 33

To build this project, accept the SDK license agreements and install the missing components using the Android Studio SDK 
Manager.

What's happening here is Gitlab pipeline is creating a docker image from our new dockerfile and running the Android build using this docker image. However the docker image is out of date so when it looks for Android SDK 33 it can't find it and tries to install it, but this fails because it can't find an accepted license for that SDK. Not a big deal, I've played with sdkmanager before we can easily update this docker image.

So looking at our newly generated docker file I can see this is badly out of date. It's using the old android-sdk tool which has been replaced by sdkmanager and it's using SDK 28 and some old build tools. Also it's running off jdk 8 which I'm sure we can improve on.

I won't go through every step I took as this would be a really long post, but I'll post some of the things I learnt along the way.

Firstly playing with Docker was really fun, but it was also quite frustrating. Constantly building and re-building took a while. What I found faster was to install Docker desktop and run it locally. This also means you can mount your docker image and run bash to debug your environment.

Also I learnt that a docker file is not docker compose. A docker file is just a dockerfile and you use this to create an image using the docker build command. Docker compose is the yml or yaml file. This allows you to create multi-container applications. Think of this a bit like config for the docker container that is running your docker image.

Another thing I learnt was that Android sdkmanager is fussy and you need to be quite precise with paths and directories or the toys and the pram part ways. The license problem mentioned above ended up being really frustrating. What I eventually found was I had to make sure the sdk wasn't in the root folder and just giving /sdk write permissions wasn't enough. I had to explicitly grant the /sdl/licenses folder write permissions. After many many runs that straightened all that out.

Below is my new dockerfile for Gitlab. You'll notice the following big differences from the stock file I got from Gitlab:

  • It now uses JDK11
  • It uses SDK 33
  • It uses sdkmanager 
  • It updates sdkmanager as it goes
  • It should (hopefully) be able to install the latest SDK and accept the license at build time
I'm wondering if I should pass in the SDK and Android build tools versions from the yaml file. I'm also wondering about installing gradle in the docker file as this takes a few seconds each time.


# This Dockerfile creates a static build image for CI
#!/bin/sh

FROM openjdk:11-jdk

# Just matched `app/build.gradle`
ENV ANDROID_COMPILE_SDK "33"
# Just matched `app/build.gradle`
ENV ANDROID_BUILD_TOOLS "30.0.0"
# Version from https://developer.android.com/studio/releases/sdk-tools
ENV ANDROID_SDK_TOOLS "8512546_latest"
ENV ANDROID_HOME /opt/sdk
ENV ANDROID_SDK_PATH /opt/sdk

# install OS packages
RUN apt-get --quiet update --yes
RUN apt-get --quiet install --yes wget apt-utils tar unzip lib32stdc++6 lib32z1 build-essential ruby ruby-dev

# We use this for xxd hex->binary
RUN apt-get --quiet install --yes vim-common
# create sdk directory, install Android SDK
# https://dl.google.com/android/repository/commandlinetools-linux-8512546_latest.zip
RUN mkdir -p /opt/sdk
RUN wget --quiet --output-document=android-sdk.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}.zip && \
mv android-sdk.zip /opt/sdk && \
cd /opt/sdk/ && \
unzip android-sdk.zip

#Sort out the mess - https://stackoverflow.com/a/65262939
RUN mkdir /opt/sdk/cmdline-tools/tools && \
mv /opt/sdk/cmdline-tools/bin /opt/sdk/cmdline-tools/tools/ && \
mv /opt/sdk/cmdline-tools/lib /opt/sdk/cmdline-tools/tools/ && \
mv /opt/sdk/cmdline-tools/NOTICE.txt /opt/sdk/cmdline-tools/tools/ && \
mv /opt/sdk/cmdline-tools/source.properties /opt/sdk/cmdline-tools/tools/ && \
export PATH="${PATH}:/opt/sdk/cmdline-tools/tools/bin:${ANDROID_HOME}"

RUN chmod -R 777 /opt/sdk

RUN /opt/sdk/cmdline-tools/tools/bin/sdkmanager --licenses
RUN echo y | /opt/sdk/cmdline-tools/tools/bin/sdkmanager --update
RUN echo y | /opt/sdk/cmdline-tools/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}"
RUN echo y | /opt/sdk/cmdline-tools/tools/bin/sdkmanager "platform-tools"
RUN echo y | /opt/sdk/cmdline-tools/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}"
RUN echo y | /opt/sdk/cmdline-tools/tools/bin/sdkmanager "extras;m2repository;com;android;support;constraint;constraint-layout;1.0.2"
RUN echo y | /opt/sdk/cmdline-tools/tools/bin/sdkmanager "extras;android;m2repository"
RUN echo y | /opt/sdk/cmdline-tools/tools/bin/sdkmanager "extras;google;google_play_services"
RUN echo y | /opt/sdk/cmdline-tools/tools/bin/sdkmanager "patcher;v4" "tools"
RUN chmod -R 777 /opt/sdk
RUN chmod -R 777 /opt/sdk/licenses
RUN yes | /opt/sdk/cmdline-tools/tools/bin/sdkmanager --licenses
RUN chmod -R 777 /opt/sdk

# install FastLane
COPY Gemfile.lock .
COPY Gemfile .
RUN gem install bundler -v 1.16.6
RUN bundle install


Anyway, I really hope this post is of use. I've certainly enjoyed learning about Docker.











29 September 2021

RxJava filter an Observable list

I had an observable with data type list, and I wanted to filter the objects in that list and maintain the observable. Sounds simple enough right, but it took a while and I wanted to record it as I've used this concept a few times now.

So let's start with our object of which we will create the list.

data class Muppet(var name: String, var age: int)

Now let's create a list of those objects

val myList = Observable.just(arrayListOf<Muppet>())

Simple stuff so far. Now let's get to the interesting Rx goodness. 

What we need is to extract the individual objects so we can filter them, so we need an observable of objects rather than an observable of a list. 

.flatMap { iterable: List<Muppet> -> Observable.from(iterable) }

So here we use flatmap to change the data type and we use the very clever Observable.from to change the data to a single result.

Now we can filter as our heart desire

.filter { muppet: Muppet -> muppet.age > 30 }

and put it back together using toList.

Here's the complete example

return getMuppets()
       .flatMap { iterable: List<Muppet> -> Observable.from(iterable) }
       .filter { muppet: Muppet -> muppet.age > 30 }
       .toList()

07 November 2020

Android Hilt example and a unit test (with Robolectric)

Android Hilt has been released and I must say I'm quite impressed. I absolutely hate Dagger which I think comprises of far too much boiler plate code, its badly documented and incredibly confusing. Hilt is of course built on top of dagger and I think this is a huge win for the Android community. Hilt decreases confusion, increases code generation and is nice and simple.

I don't want to go into what Dependency Injection (DI) is, that's been done to death.

There are two resources I found really useful to get started with Hilt:

The Hilt documentation - https://dagger.dev/hilt/

Coding with Mitch videos: https://www.youtube.com/watch?v=zTpM2olXCok

There are two things I want to talk about in this blog post:

  1. A really quick intro and setup of a Hilt module.
  2. A unit test using Robolectic.
Before I go much further I would discourage you from using Robolectric. It's become a real pain to work with, requiring Java 9 for its latest version and regularly deprecating things without clear documentation on how to migrate. That said I've been using Robolectic for a while so I'm stuck with it until I can remove it from my code. I'm hoping Hilt and ViewModels will help me do this.

The first thing I wanted to look at is (I think) a fairly standard use case where a class has some dependencies and we want to use that class in our activity without creating it every time.

Step 1 - The application


@HiltAndroidApp
class MyApplication : Application()

Step 2 - The Class


class Petrol(val input: String) {

    fun getTheInfo(): String {
        return input
    }
}

Step 3 - The Module

Now this is the interesting bit, here we define a "factory" where we list how the class is to be created. Obviously this is a very simple example, but I think that's a good way to start.

@InstallIn(ApplicationComponent::class)
@Module
class PetrolFactory {

    @Provides
    fun getPetrol(): Petrol {
        return Petrol("Petroll")
    }
}

Step 4 - The Activity


Now we setup the activity and you'll note how little code we need to write to create and start using the class

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var petrol: Petrol

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(findViewById(R.id.toolbar))
        
        findViewById<TextView>(R.id.main_text).text = petrol.getTheInfo()
    }
}

I think this is fantastic and hopefully it's clear to see how easy this is and how quick you can make complex interactions really simple.

Step 5 - The Tests


With the tests I want to show how you would pass in something different for the purpose of testing. 

@UninstallModules(PetrolFactory::class)
@HiltAndroidTest
class MainActivityTest : RobolectricTestConfig() {

    @get:Rule
    var hiltRule = HiltAndroidRule(this)

    @Module
    @InstallIn(ApplicationComponent::class)
    class TestEngine {
        @Provides
        fun getPetrol(): Petrol {
            return Petrol("TestPetrol")
        }
    }


    @Before
    fun init() {
        hiltRule.inject()
    }

    @Test
    fun testActivityMainText() {
        val scenario = ActivityScenario.launch(MainActivity::class.java)

        scenario.onActivity {
            assertEquals("TestPetrol", it.findViewById<TextView>(R.id.main_text).text)
        }
    }
}


One thing that's really important to remember is you can't run the test with the green arrow in Android Studio. You need to use gradle. So just run the gradle task instead. That's it I hope that helps demonstrate how you would create a DI app with Hilt in a few easy steps. I also hope you can see how easy that makes testing.










08 January 2020

Android library using GitLab as an aar repository


As your Android project begins to grow you will likely start to accumulate a few libraries. Not the official ones, like rxjava, androidx and so on, but I'm talking about ones you've developed yourself. These could be your entire network layer, your really really useful utils folder, or whatever. If you're a good developer you'll have these in separate modules or possibly even completely separate projects. To start with this is fine, you may be copying your aar files around or just being vocal about changes to your modules so your teammates can keep up to date.

This article gives a fantastic explanation of why it's a good idea to stop doing that:
https://inthecheesefactory.com/blog/how-to-setup-private-maven-repository/en

The author makes some excellent points why modules and copying aars are a bad idea. The ones I most agree with are:


Library distributed to other developers should not be modifiable. In case there is some problem, issue should be reported to developer involved to let them fix. Letting other developers directly access the source code might cause them accidentally doing a quick fix by themselves which will cause a big problem afterwards. 
Most of the problem listed above are solved except one: it's still hard to update and roll back over the version.

The solution is to use a repository. Basically give your library a specific version and deploy it to a repository so it can be referenced simply using nothing more than a gradle import. Luckily for us there are a few repositories out there and they can be easily made private. So we only allow permitted developers access to our libraries.

Jfrog is a great way to go, but this means you've probably got to manage a server yourself. What I was hoping for was a cloud method where I had no server admin and no setup process. So I turned to my old friend GitLab.

Now the first and most important thing to note is that the GitLab repository pattern (named packages) is not free. GitLab Packages is only currently available on the Silver package, which does incur a monthly cost. Although they do have a trial period, if you just want to give it a go. There is a project level setting to enable packages, but for me this was already turned on.

So that said, let's get to the code. First step is to create a library.

I created an Android library named "WorstLibEver" this is the extent of it:

public class HodorUtils {
    public String askHodor(String question){
        return "Hodor";
    }
}

I know, you're blown away aren't you?

Now we need to edit our gradle file:

First add this line to the top:

apply plugin: 'maven-publish'


Next you'll need to add the following to the bottom:

task sourceJar(type: Jar) {
    from android.sourceSets.main.java.srcDirs
    classifier "sources"
}

publishing {
    publications {
        bar(MavenPublication) {
            groupId 'com.test'
            artifactId 'worstlibever'
            version '1.7'
            artifact(sourceJar)
            artifact("$buildDir/outputs/aar/app-release.aar")
        }
    }
    repositories {
        maven {
            url "https://gitlab.com/api/v4/projects/zzz/packages/maven"
            credentials(HttpHeaderCredentials) {
                name = "Private-Token"
                value = GITLAB_PERSONAL_TOKEN
            }
            authentication {
                header(HttpHeaderAuthentication)
            }
        }
    }
}

Note that zzz refers to the project ID. This is the numeric ID from your gitlab main page.

One other thing I needed to do was upgrade to gradle version 5.5 in the gradle-wrapper.properties. Not doing this meant gradle couldn't find HttpHeaderCredentials.

You now need to go into GitLab and generate an access token. You do this is the user settings (not the project settings) and ensure the API access level is ticked. Copy the access token and add it to your gradle.properties file.

GITLAB_PERSONAL_TOKEN=xxx

That should be all you need. Clean, build and assemble your library and then run the gradle publish command. Your aar should be deployed to GitLab packages and it should now be ready for downloading. You can check in your GitLab packages section and you should see the details displayed there.

Now we goto our client, the app that is going to download and read our worst ever library. We open up our gradle file and add the following:

buildscript {
    repositories {
        google()
        jcenter()
        maven { url 'https://maven.google.com'}
        maven {
            url 'https://gitlab.com/api/v4/projects/zzz/packages/maven'
            credentials(HttpHeaderCredentials) {
                name = "Private-Token"
                value = GITLAB_PERSONAL_TOKEN
            }
            authentication {
                header(HttpHeaderAuthentication)
            }
        }
    }
}

allprojects {
    repositories {
        maven { url 'https://maven.google.com' }
        maven {
            url 'https://gitlab.com/api/v4/projects/zzz/packages/maven'
            credentials(HttpHeaderCredentials) {
                name = "Private-Token"
                value = GITLAB_PERSONAL_TOKEN
            }
            authentication {
                header(HttpHeaderAuthentication)
            }
        }
    }
}


Lastly we add in our dependency and we should be away:

implementation 'com.test:worstlibever:1.7'

That's it, happy coding!








27 October 2019

Enrol on App Signing in the Google Play Store for existing Android App

App Signing by the Google Play Store is on the face of it an incredibly brilliant idea. I absolutely love it. However I found it impossibly difficult to follow Google’s rather short documentation on how to get setup, especially for existing app users. So I thought I’d write this little helper to try and spell it out to anyone who tries to follow the same process I have.

App signing is the process of creating a keystore with which you sign your Android app and verify it is in fact you who is releasing an update to your Android app. A keystore remember is basically just a special store for a private and a public key.

If you lose the original master keystore you signed your app with, bad news. You need to create a new app and a new Google Play Store listing. Not fun.

The concept of App Signing for Google Play Store is basically that you surrender the master keystore that you used to sign your initial app to Google (let’s call this the release key). Google then securely stores and manages your release key. You then create a brand new keystore called an upload key. You do this as if you were creating a brand new app, through Android Studio or if you’re a keyboard warrior, using java keytool. You then tell Google about this upload key and you’re done. All future releases can be signed with your upload key and Google will re-sign with the original, now super secret and secure release key. The benefit of this is Google keeps everything secure, and if you lose or compromise your upload key, Google will let you discard it and create a new one. Your users are none the wiser and everyone is happy all of the time.

The process for implementing all this is a little more complicated, first you need to head the play store and goto “App signing” under “Release management”.




Now we need to get all our keystores, private keys, certificates and koala bears in some sort of order. I’m doing this in Android Studio 3.5, older versions may need an upgrade.

First step we need to extract the private key from your original master release key.

Step 1 - Get an App Signing Private Key

Open your app in Android Studio and ensure it builds

  1. Goto Build -> Generate Signed Bundle / APK (Don’t worry you don’t need to actually release this build)
  2. Even if you’re not wanting to use App Bundle, select Android App Bundle and click next.
  3. Enter the details for your master, original release key, including store password, alias and key password
  4. IMPORTANT -> check “Export encrypted key for enrolling published apps in Google Play App Signing”
  5. Finish off the process and make a build. You should now have a pepk file which is the private key for your original master release key.


Now we need to generate a brand new upload key to use to sign our future apps.

Step 2 - Create an upload key


  1. Again goto Build -> Generate Signed Bundle / APK (Again not actually going to release an app)
  2. Select APK or App Bundle, whichever you would normally do.
  3. Click “Create new…”, we’re going to create a new keystore.
  4. Fill in all the details and make the build.
  5. You should now have a brand new keystore with brand new passwords and an alias. KEEP THIS CAREFULLY.
  6. This keystore will be used forevermore as your main key to release your apps.


Now we need to get a public key from our new upload key, this will be how we tell Google about our new upload key.

Step 3 - Generate a Public Certificate


  1. Use keytool and run a command like this
    keytool -export -rfc -keystore upload-keystore.jks -alias upload -file upload_certificate.pem
  2. Use your NEW UPLOAD KEY for “upload-keystore.jks” and your new alias 
  3. Keytool is usually somewhere in jdk/bin
  4. After running this command you should have a .pem file.


Now go back to the Google Play Store and click “Upload a key exported from Android Studio” now you can add your private key and your public certificate. This gives Google the details of the release key and the upload key and you should be all set.

Click finish and you should now be signed up to App Signing. Well done, keep a close eye on that upload key and it’s passwords.

15 June 2019

Mockito 2.0 and a thousand failing unit tests

Wow this killed me for a few days. I upgraded my long since out of date Mockito to the latest version 2.28.2 from version 1.something. Instantly 90% of my unit tests failed. Queue a long drawn out investigation to try and figure out what was happening. Numerous culprits were held under the spotlight and shaken down.

Ultimately, (as is usually the case) the answer was somewhat simple. My mock methods had all been declared irrelevant by this change to Mockito:

anyString() no longer accepts nulls.

So a mock method like this:

when(mockClass.mockMethod(anyString())).thenReturn("All your base are belong to me")

Simply stopped returning anything.

https://github.com/mockito/mockito/issues/185

The workaround is either to use any() or a deliberate null.

when(mockClass.mockMethod(any())).thenReturn("All your base are belong to me")


when(mockClass.mockMethod(isNull())).thenReturn("All your base are belong to me")


I hope this helps someone avoid my mistakes. Happy coding.

01 May 2019

Kotlin Coroutines

I don't by any means propose to be a master on this topic, but there's been so much chat on this topic on the blog world, I thought I'd give it a try. As is my usual I always like to start with the most insanely simple scenario I can think of.

I started off reading this article which actually gives a really nice overview of what coroutines are and why they're important:
https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the-background-3e0e54d20bb

I started by creating a new Kotlin project, I'm on Android Studio 3.3.2 and Kotlin version 1.3.21 and added the following library:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'

Maybe this will be added into standard Kotlin in the future. For me this was quite a surprise
that it needed a separate library.

Now let's jump straight into some reckless coding, I added a TextView to my activity with the ID 

android:id="@+id/hello_world"
Now let's create a function that will run in the background. I'm thinking of this like the main thread on an AsyncTask. For my function, I'm going to pause for ten seconds and then return a string.

suspend fun get(): String {
    //Delay for ten seconds
    delay(10000)
    return "All your base are belong to me"
}
Now a plain vanilla function to show the results
fun show(result: String) {
    val tv = findViewById<TextView>(R.id.hello_world)
    tv.setText(result)
    println("Done!")
}
Now we need to setup the Activity to allow for Coroutines. This in my opinion is a bit of a mess. I've no idea why I need all this boilerplate nonsense. Oh well
class MainActivity : AppCompatActivity(), CoroutineScope {

    private var job: Job = Job()

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }
You'll see we need to implement the CoroutineScope and add some other fluff just to use Coroutines. Lastly we can call our function in the background from our onCreate method

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    launch {
        // Dispatchers.IO
        val result = get()
        // Dispatchers.Main
        show(result)
    }

    println("Start!")
}
So as you can see onCreate launches get in the background which waits ten seconds then returns a string. The launch method then, (when complete) calls the show method with that result. I know that's an incredibly basic example but I hope it helps. I found the concept easy to grasp, but the implementation was a bit fiddly. Hence the blog post.

22 November 2018

Android RxJava .delay() method


This is a weird one but it took up most of my day, so I thought I'd post it here for posterity. This article describes the problem very well:

https://dev.to/dbottillo/rxjava-a-story-about-delay-and-schedulers-j48

.delay(200, TimeUnit.MILLISECONDS)

In short calling .delay on a rxjava observable causes very strange results. The reason is because delay forces a change to the subscribeOn type. Why it does this is a mystery to me. However luckily the solution is very simple.

.delay(200, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())




12 July 2018

Firebase Realtime Database - Custom Rules - Admin Write Permissions


Firebase is cool. Firebase realtime database is also cool. Rules...they are way less cool.

I joke, but protecting your Firebase realtime data is of course important and not to be under-estimated. I wanted to setup Firebase realtime database rules so only the database administrator (me) could enter or edit data. This took me a while to figure out as it involves not only setting the correct rules, but editing the metadata of the user.

Right so let's jump straight in, we're going to set the database rules to allow only edits by an administrator:

"rules":{
        "dinosaurs":{
            ".read":"auth != null",
            ".write":"auth.token.admin === true"
        }


You can see we've granted write permissions only if the user is an administrator. That was pretty easy. Sadly actually configuring which user is an admin is much harder.

First goto your Firebase console and click on the Authentication section, you should be on the user's tab. Locate the user you want to make an administrator and copy their User UUID.

To set this user as an administrator you'll need to use the Firebase Admin SDK. This isn't an Android SDK and so I'm afraid we're going to have to break into some NodeJS.

I'm not going to go into setting up NodeJS here, as it's a huge pain in the neck. Once you've got NodeJS setup, install the firebase Admin SDK as such:
https://firebase.google.com/docs/admin/setup?authuser=0

$ npm install firebase-admin --save

Now you'll need to return to your Firebase console and create a service key. In the settings section, goto service accounts and download a private key. You'll need to create one for NodeJS and you'll need to copy it to the NodeJS folder you're about to create. The path from the index.js file needs to be relative, mine is justin the root. As we won't be sharing this project, it should be fine, but you should never publicly host or share your private key.
Now you should be ready to run a NodeJS script which will set this user as an admin.

Create a standard node project and edit the index.js file. Add the following code:

var admin = require('firebase-admin');

var serviceAccount = require('./service_key.json');

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: '*****'
});


admin.auth().setCustomUserClaims('#######', {admin: true}).then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});

Replace the *** with your database url and #### with the UUID you copied earlier and run your nodeJs project.

That's it. Your user should now be an administrator, it can take a while to propagate, so try logging on and off a few times if it doesn't work right away.

What you've done here is set a custom Claim on that user. Basically some meta-data that describes that user as an admin. Your database rules will only allow administrators to edit that data. Here's a bit more info should you wish to take Calims further:
https://firebase.google.com/docs/auth/admin/custom-claims?authuser=0

Good luck, and have fun with Firebase.

31 October 2017

RxJava Publish Subject is pretty awesome


Hopefully you're all now using RxJava because it's pretty awesome. Now I'm not about to force on you another tutorial, there are plenty out there. I've been using RxJava for ages now and only just discovered a new thing. I love discovering new things, especially about something I've used for ages. So I discovered PublishSubject, this is basically an amazing alternative to creating callbacks all over the place.

Normally if I were to implement a callback from a fragment to an activity I'd do something like this:

public interface IFragmentListener{
    void onFragmentSuccess();
}

private IFragmentListener mFragmentListener;

Then of course my activity would implement that interface and receive callbacks when mFragmentListener is called.

However there's a better way to do things, and that's with publishSubject.

private PublishSubject<String> mSelectionObservable = PublishSubject.create();

mSelectionObservable.onNext("Hello");

public Observable<String> getSelectionObservable() {
    return mSelectionObservable;
}

PublishSubject allows you to declare an observable which you can subscribe to and send callbacks whenever you want. As you can see when I call mSelectionObservable.onNext().

Your activity can subscribe to the publishSubject like this

frag.getSelectionObservable()
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread())
        .doOnNext(new Action1<String>() {
    @Override
    public void call(String s) {
        Log.d(TAG, "call: ");
        Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();
    }
}).subscribe();

Hope that helps make your code more awesome!

18 January 2017

Amazon AWS Key Management Service with Android


I recently came across Amazon AWS's new Key Management Service (KMS). This seemed like a pretty cool idea so I thought I'd give it a go and see if I could get Amazon to manage my keys on Android.

The idea is pretty straightforward, Amazon host your secure keys for you, so you can encrypt and decrypt without having to worry about key management and storage. As far as I know KMS currently supports only symmetric encryption.

Here's how to use the Amazon Web Services console to create a Key Management Service (KMS) key:

  1. Goto AWS management console and click services, under Security and Compliance click IAM
  2. Goto groups on the left menu and click create new group
    1. Create a group and name and click next
    2. Search for “AWSKeyManagementServicePowerUser” and check it, click next
  3. Goto users on the left menu and click add user
    1. Give the user a name “kms_user” or something
    2. Click programatic access
    3. You should see your group in the add user to group section, check this
    4. Click next
  4. Click Encryption Keys at the bottom
    1. Give the key a name
    2. Do not give the key any administrators, for this tutorial the account owner will be the only one who can administer this key. Click next.
    3. Give your kms_user user account use permissions for the key
    4. Click next and finish adding the key
  5. Setup Android Studio
    compile 'com.amazonaws:aws-android-sdk-kms:2.2.+'


You should now have an encryption key and be ready to start coding. For the Android part of this I used two Async tasks. You could do this in a service but it needs to be off the main UI thread as it's a network call.



public class AsyncEncrypt extends AsyncTask<String, String, ByteBuffer>{

    private static final String TAG = AsyncEncrypt.class.getSimpleName();

    public interface AsyncEncryptListener {
        void processFinish(ByteBuffer cipherText);
    }

    private AsyncEncryptListener listener;

    @Override
    protected ByteBuffer doInBackground(String... strings) {

        final AWSCredentials creds = new AWSCredentials() {
            @Override
            public String getAWSAccessKeyId() {
                return "xxx";
            }

            @Override
            public String getAWSSecretKey() {
                return "yyy";
            }
        };

        AWSKMSClient kms = new AWSKMSClient(creds);

        String keyId = "zzzzz";
        ByteBuffer bytePlainText = ByteBuffer.wrap(strings[0].getBytes());

        EncryptRequest req = new EncryptRequest().withKeyId(keyId).withPlaintext(bytePlainText);
        ByteBuffer ciphertext = kms.encrypt(req).getCiphertextBlob();

        Log.d(TAG, "onCreate: " + ciphertext.toString());

        return ciphertext;
    }

    @Override
    protected void onPostExecute(ByteBuffer cipherText) {
        super.onPostExecute(cipherText);
        if(listener != null){
            listener.processFinish(cipherText);
        }
    }

    public void setListener(AsyncEncryptListener listener){
        this.listener = listener;
    }
}




Decrypt:


public class AsyncDecrypt extends AsyncTask<ByteBuffer, String, String>{

     private static final String TAG = AsyncDecrypt.class.getSimpleName();

     public interface AsyncDecryptListener {
        void processFinish(String plainText);
    }

     private AsyncDecryptListener listener;

     @Override
    protected String doInBackground(ByteBuffer... ciphertextBlob) {

         final AWSCredentials creds = new AWSCredentials() {
            @Override
            public String getAWSAccessKeyId() {
                return "xxx";
            }

             @Override
            public String getAWSSecretKey() {
                return "yyy";
            }
        };

         AWSKMSClient kms = new AWSKMSClient(creds);

         DecryptRequest req = new DecryptRequest().withCiphertextBlob(ciphertextBlob[0]);
        ByteBuffer plainText = kms.decrypt(req).getPlaintext();

         String decoded = new String(plainText.array());
        Log.d(TAG, "onCreate: " + decoded);

         return decoded;
    }

     @Override
    protected void onPostExecute(String plainText) {
        super.onPostExecute(plainText);
        if(listener != null){
            listener.processFinish(plainText);
        }
    }

     public void setListener(AsyncDecryptListener listener){
        this.listener = listener;
    }
}

That's pretty much it. You create a set of AWS credentials supplying the security data given to you in the console for your user, then pass those credentials to the KMS client and make an encryption request.

The only other thing you might want to consider is whether it's worth it or not, in order to use KMS on Android you've got to store your secret key and access key somewhere. If an attacker can get those, they can access your encryption key. It's the classic chicken and egg scenario that distributed systems suffer from again and again. Oh well, it was a neat experiment.

At least it means it's much easier to rotate keys without having to re-release a new app!

30 December 2016

Improving on SimpleDateFormat


I'm a big fan of SimpleDateFormat, but it suffers from one critical problem. Your date format is forced on the user. I've learnt the hard way (as have most developers) that American's have their own date format (mm/dd/yyyy), us Brits have our own format (dd/mm/yy) and of course there are many other countries that also have different ideas.

Using SimpleDateFormat means you pick a format and the user has to like it. In some cases this could even be very frustrating for the user. You could of course allow them to pick their own format, but that's a lot of work.

My point here is Android has a little used method of doing this hard work for you and it utilizes the user's locale as set in the phone settings, to calculate this format. That is java.text.DateFormat.
So if the user has English American locale, then DateFormat will use that, if it has en-UK then that's the format it'll use. Magic!


import java.text.DateFormat;
DateFormat dateTimeFormat;


dateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault());


Date date = new Date();
dateTimeFormat.format(date);


Next time you're displaying a date or a time, why not try using DateFormat instead of forcing your format on the user?

22 December 2016

Inject Javascript into Android WebView

Here's an interesting one I stumbled across the other day. The ability to "inject" some javascript into a webview and override the existing javascript for a webpage.

Let's say you want to override an existing javascript function, maybe one that's broken or you just want to change functionality. This is possible using onPageFinished.

Now I'm not going to say you *should* do this, nor will I say it is recommended or a good idea. I'm just pointing out that it's possible and saying it's mildly interesting.
Obviously there are warnings that go with enabling javscript on your webview and you should take heed of them over my example here.

Here's my HTML that I will load in a webview. For this example I've loaded it locally from my assets folder. I see no reason why this wouldn't work on a remote page.


<html>
    <head>
        <title>hello</title>
        <script>
            function myFunction() {
                document.getElementById("demo").innerHTML = "Gonzo was here";
            }
        </script>
    </head>
    <body>
        <p>
            <button name="Sumit" label="Submit" value="Submit" id="Submit" onclick="myFunction()">Submit</button>

            <br /><br />
            <div id="demo">Hello</div>
        </p>
    </body>
</html>


Let's try and change that javascript function to do something else:


final WebView webView = (WebView) findViewById(R.id.webView);

webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        webView.loadUrl("javascript:function myFunction(){document.getElementById(\"demo\").innerHTML = \"Paragraph changed.\";}");
    }
});

WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.loadUrl("file:///android_asset/test.htm");


As you can see we've injected a custom function that overrides the results with a different output. Interesting huh?

13 November 2016

Android Round Launcher Icon


So the Google Pixel now allows you to use a round icon as your launcher icon. I've seen incredibly few apps implement this, even most Google apps have yet to update. That got me wondering how to achieve this new amazing (not at all copied from Apple) roundness?

The answer is this new tag in your manifest application:

<application
    android:icon="@mipmap/ic_launcher"
    android:roundIcon="@mipmap/ic_round_launcher"

round launcher icon

square launcher icon


Android is clever enough to apply this only to phones that support it and lets you
provide a normal boring square icon for old phones.

You are given this error in the IDE which is annoying:
  
    Resources referenced from the manifest cannot vary by configuration 
    (except for version qualifiers, e.g. -v21.) Found variation 
    in hdpi, mdpi, xhdpi, xxhdpi, xxxhdpi
  
It can safely be ignored or suppressed though.

25 October 2016

Google Pixel Review


Everyone and their dog seems to be putting out Google Pixel reviews so I thought I'd chip in with my thoughts and findings.

First thing I discovered is you need a nano sim. Grr, hadn't thought about that. Thankfully BT were amazing and I had one less than 24 hours later.

The phone is smaller than I expected, much smaller. I hate big phones and that's part of the reason why I've stuck with my Nexus 4 for four years. I was genuinely worried about it, but within a few hours I had gotten used to the Pixel's larger length and I have no regrets. It's incredibly small in depth and with is small too, so the larger height is well compensated for.

The phone itself is beautiful, the curves are nice and it feels very comfortable. The metal finish is a joy to feel and the screen blends well into the case. The back glass surrounding the camera and fingerprint reader is odd, I don't quite get it. It spoils the design a little and I don't know why it's not all metal, but it's not of any real consequence.

The screen is stunning, absolutely incredible, again I'm comparing most of this to my Nexus 4 so it is worlds apart. The colours and display are crystal clear and I sometimes find myself just staring at it.

The speed is lightening fast and it seems to cope with whatever I can throw at it with ease. I haven't really pushed it yet but it is so responsive and quick I can't see it struggling. The battery is good, compared to my four year old phone it lasts infinitely longer, but it's no more or less than I'd expect from a modern phone. It doesn't last weeks but it'll get me through a couple of days.

The USB C is a cool feature, but sadly it's not new nor unique, it works and it charges fast, actually it charges really really fast. Plus you're less likely to destroy your phone by ramming the charging cable in upside down. The fingerprint reader isn't new either, but frankly that's rocked my world! I love it.

Now the OS is a difficult one, it's fine and I have no complaints. However ...I am an Android developer and have used phones by every manufacturer you care to name and every Android OS extensively. I think we're well beyond the point where an OS update makes any real difference. In terms of speed and battery use, they've pretty much done all they can. What we see now is minor updates and UI tweaks like the settings changes. Not since Material design has anything really made much difference to the user. I'm not unimpressed, it just hasn't changed my interactions with the phone much at all.

There are a few things with confuse me and they are largely the things they've "borrowed" from Apple:

  • The round icons, not sure it makes a lot of difference, but what do we developers do? Can we release with round and square icons? If we switch to square, what happens to the old OSs that aren't prepared for round icons?
  • Quick Tap or whatever you call it where you can long press on a launcher icon? That's just a blatant rip off, and it adds nothing to the user experience. Bah!


Lastly is the Google Assistant, this is impressive! Its learnt my voice and ignores my girlfriend's, which I love! It understands easily what I'm asking it and responds quickly and generally with a surprising insight. That said....I'm still not going to talk to it!

So there are my highlights, in short it's a fantastically well put together phone and I'm really enjoying it. Go get one.

03 September 2016

Android Favourite Star


So I recently needed a favourite button and I thought Android's classic star button would fit the bill quite nicely. So I found this stack overflow page which sounded like just what I needed:

http://stackoverflow.com/questions/8244252/star-button-in-android

I create my ImageView and set the src to @android:drawable/btn_star

However I came across one big problem, I couldn't for the life of me get the on state to stick. I tried setSelected but nothing happened. I looked at the drawable in the Android package and it seemed what I was really looking for was setChecked, but an ImageView doesn't have a checked state. Nor does a Button, nor a ImageButton and a CheckBox doesn't have a src. So I was stumped.

After some time I found what I needed

        <CheckBox
            android:id="@+id/item_star"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:contentDescription="@string/favourite"
            android:duplicateParentState="true"
            android:button="@android:drawable/btn_star" />


I used a checkbox with a button value set to the Android drawable. Hope it helps :)

This content is copyright and use limited to https://webdeveloperpadawan.blogspot.com/

30 May 2016

Android Lambda

I've heard a lot about lambda recently and how amazing Java 8 is. I'm not really sure this will have a huge impact on Android as I fail to see the relevance, none the less I was interested and keen to experiment.

I thought I'd create a super simple example by changing the OnClickListener of a button and trying to replace it with a lambda. Nothing shocking here, just wanted to know if I could. So I created a brand new project and here's how I got it working.

Before we go any further you'll need to:

  • Donwload the Android N SDK, 
  • Download JDK 1.8 (and target it with Android Studio) 
  • You'll need an emulator or device capable of running Android N.

Android N

To use lambdas we have to target Android N so I've updated the compile version, build tools, min Sdk and target Sdk.

android {
    compileSdkVersion 'android-N'
    buildToolsVersion '24.0.0-rc3'

    defaultConfig {
        applicationId "eightest.test.com.eighttest"
        minSdkVersion 'N'
        targetSdkVersion 'N'
        versionCode 1
        versionName "1.0"
    }

The code

This is what we would normally do for a onClick
findViewById(R.id.activity_main_text).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        showToast()
    }
});

This is how a lambda makes it look a little cleaner
findViewById(R.id.activity_main_text).setOnClickListener((View v) -> {showToast();});

Target Java 8

We need to specifically target java 8 (do this inside the Android brackets, after buildTypes)
    compileOptions {
        targetCompatibility 1.8
        sourceCompatibility 1.8
    }

Utilise Jack compiler

Now we need to tell Android to use the Jack compiler which will allow us to utilise Java 8.
    defaultConfig {
        applicationId "eightest.test.com.eighttest"
        minSdkVersion 'N'
        targetSdkVersion 'N'
        versionCode 1
        versionName "1.0"
        jackOptions {
            enabled true
        }
    }

This content is copyright and owned by https://webdeveloperpadawan.blogspot.com/

25 May 2016

Firebase Tutorial For Android Developers


I've sadly never had the chance to use Firebase in a production app. After Google talked about it so much at this year's IO I though it was about time I had a play and created a tutorial.

Firebase is primarily a real time database that makes data storage simple and easy. In the last few years it's been expanded to be an incredible fleet of tools that Android, iOS and Web developers can make use of.

The Firebase database is an inspired solution to data storage and syncing. The Firebase db is a NoSQL, cloud hosted database that allows for fast and easy sync across different devices. No more clumsy sync adapters or troublesome push notifications, Firebase handles all this for you with a simple API.

Also included in the suite is analytics, crash reporting, file storage, authentication, remote config and more.

I know I'm beginning to sound a bit like a marketing rep, but I really must say I'm flawed by how awesome Firebase is. The cloud sync database is really simple to set-up and it works incredibly well.

OK let's get into a tutorial. I wanted to link my app to Firebase and create a cloud db. Sounds easy! First you will be required to have a Google account, then we goto Firebase and create a new project:

https://console.firebase.google.com/

Now you have a Firebase app. Next we need to add it to Android, the wizard walks you through this process with a lovely little Material designed guide. You'll need the package name of your app and then to make some gradle and config changes.

Now we need to disable authentication. If you goto the Firebase console and click database on the left hand side you should see an empty data set. In the tabs above click on rules and change the json to match this:

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

This basically disables all authentication giving anyone write and read access. For a real application you'd want to set-up proper user role based access, but for this tutorial will just turn authentication off.

Next you'll need to setup your Android project. You should already have the json config file and the gradle additions. Now you need to add the database gradle dependancy:

compile 'com.google.firebase:firebase-database:9.0.0'
That's it, you're pretty much ready to go. However there are a few important db constraints to get your head around before you launch your new app.

Data in Firebase is all JSON but it does support saving objects, lists, maps as well as simple data types such as strings, booleans, doubles etc. It seems to me largely based around a key value pair type of system. You put a value with it's corresponding key to Firebase and it syncs it. Then use the same key to retrieve that data.

SetValue

SetValue is an assignment method. At first I thought you could use it to create new db entries, but that is not the case. Instead you call

FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("message");

myRef.setValue("Hello, World!");

This creates a key value pair with the key "message" and the value "Hello, World!". If you call setValue again on that key, the value is overwritten, a new pair is not created in this way.

ValueEventListener

The ValueEventListener gives you the ability to receive a callback when your data changes. This is useful when you create your UI so you can receive updates whenever the data changes. No more db listeners or broadcasts.

public void onDataChange(DataSnapshot dataSnapshot)

All you need to do is attatch the listener

myRef.addValueEventListener(this);

Push

Push allows create a list of data. Using this method generates a unique key instead of forcing you to create a new key for every object.

Save Data

Here's how I added a list of simple objects. Player is just a simple pojo. We use push to create a new child of the DB_KEY_PLAYERS type. We then get the key of that new child.
Using that key we can now update it's data. The second param to setValue is a callback so we can update the UI when the data has been updated successfully.

FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myplayers = database.getReference(DB_KEY_PLAYERS);

//Get key for a new player
String key = myplayers.push().getKey();
Player player = new Player(playername);
//set value of new player
myplayers.child(key).setValue(player, ActivityAddPlayer.this);


That's it, you should now have a working cloud synch'd database. You can now add analytics and crash reporting as well, super simple and very well designed. I really hope I get to put it into practice in a live app sometime soon.

This content is copyright and owned by https://webdeveloperpadawan.blogspot.com/

16 May 2016

Android - There is a problem parsing the package

This was a head scratching problem I had. I copied an apk to my phone and tried to install it but immediately got an error

There is a problem parsing the package

So I opened up Android Studio, assembled it again and re-installed it. Same problem. I checked the manifest, checked gradle and a few million other things! Some hours later I tried:

adb install C:/folder/myapp.apk

Then I checked the ADB logcat and finally I had something to go on.

Installation error: INSTALL_PARSE_FAILED_MANIFEST_MALFORMED

How frustrating! Couldn't I have been told that during build? Well at least I had something. It turned out to be a missing provider authority for a specific flavour. Oh well, next time I'll know what to do first!

03 May 2016

Android n00b lessons

I've been training a couple of relatively junior Android developers recently and I've seen a few mistakes repeated. I thoughts I'd mention them here in the hope it will help someone else. This isn't supposed to be a laugh at anyone's expense, just a discussion about how to improve your code and become a better developer.
  1. Don't close a cursor properly
    I see this in on-line examples, in old code and in new code written by juniors. The fact is there are a lot of things that can go wrong when using a cursor, so you need to be a bit careful and make sure you don't throw an error or waste memory. 
    1. First and foremost close the cursor.
    2. Your cursor might be null
    3. Use finally to close your cursor, it works really well!

    Cursor data = context.getContentResolver().query(MyProvider.DETAILS, null, null, null, null);
    
    try {
        if (data != null && data.moveToFirst()) {
            retVal = data.getString(data.getColumnIndex(columnName));
        }
    } finally {
        if(data != null) {
            data.close();
        }
    }

  2. Catching an error badly
    Arrrg! I see this far too often. An empty catch block. Even if it's just a Log.e, that's better than nothing. OK I'll admit there are some situations where you just don't care if an error is thrown (like above), but all too often people just throw the try in to avoid compile errors and leave the catch empty. Don't do it!

            try {
                int a = 1;
            }catch(Exception e){
                    
            }
    

  3. Excessive use of RecyclerView
    RecyclerView is new, it's cool and it's heavily publicized. That doesn't mean you should use it ALL the time. If you've got a small simple app with one ListView that will show about three elements, please don't bring an entire new library into the project. A ListView is OK. If your ListView is small and your needs simple, don't panic, you can use a ListView. The world will not end, I promise. Yes the RecyclerView is efficient, especially when you want to use animations or change elements but sometimes it's like cutting the grass with a machine gun. I don't want to say RecyclerView is bad, it's a fantastic tool, but use your perspective and let's keep it simple people!

  4. Variables in a loop
    This one was really interesting. Conventional wisdom often states you should never create a variable in a loop, and to be honest I always held with this. However I did some research recently and it seems that it's actually most efficient to declare the variable in the smallest scope possible. If that means declaring it in the loop, then fine, as long as that's NOT then used outside the loop.

    for (Person person : people) {
        String desc = person.getDescription();
        ...
    }
    

If you've any more suggestions then I'd love to hear them.