Android Retrofit with Coroutine demo

 1. Add dependencies for the coroutine and retrofit in the gradle

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

// Retrofit 2 for network tasks
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
implementation 'com.google.code.gson:gson:2.8.2'

2. Create factory class for creating retrofit instance. RestUtil.kt

import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class RestUtil private constructor() {
    private val API_BASE_URL = "https://api.github.com/"
    val retrofit: Retrofit

    init {
        val interceptor = HttpLoggingInterceptor()
        interceptor.level = HttpLoggingInterceptor.Level.BODY
        val httpClient = OkHttpClient.Builder().addInterceptor(interceptor).build()

        val builder = Retrofit.Builder()
            .baseUrl(API_BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(CoroutineCallAdapterFactory())

        retrofit = builder.client(httpClient).build()
    }

    companion object {
        private var self: RestUtil? = null

        val instance: RestUtil
            get() {
                if (self == null) {
                    synchronized(RestUtil::class.java) {
                        if (self == null) {
                            self = RestUtil()
                        }
                    }
                }
                return self!!
            }
    }

}

3. Create data model class for holding the API result. GithubAccount.kt since we are using github api for getting github account in this demo.

import com.google.gson.annotations.SerializedName
data class GithubAccount(
        @SerializedName("login") var login : String = "",
        @SerializedName("id") var id : Int = 0,
        @SerializedName("avatar_url") var avatarUrl : String = "",
        @SerializedName("created_at") var createdAt : String = "",
        @SerializedName("updated_at") var updatedAt : String = "") {

    override fun equals(obj: Any?): Boolean {
        return login == (obj as GithubAccount).login
    }
}

4. Create Retrofit interface with coroutine for the REST service. Deferred makes it a coroutine result.

import kotlinx.coroutines.Deferred
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path

interface GithubApi {
    @GET("/users/{username}")
    fun getGithubAccount(@Path("username") username: String): Deferred<Response<GithubAccount>>
}

5. Finally, using coroutine and retrofit in the activity class.

class MainActivity : AppCompatActivity() {

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

        val githubService = RestUtil.instance.retrofit.create(GithubApi::class.java)

        // launch a coroutine for doing the network task
        GlobalScope.launch(Dispatchers.Main) {
            // slowdown the process a bit to demonstrate everything in this coroutine is sequential
            // everything outside of this launch block is not blocked by this coroutine
            delay(5000)

            val response  = githubService.getGithubAccount("google").await()
            progress_circular.visibility = View.GONE
            if (response.isSuccessful) {
                if (response.body() != null) {
                    tv_content.text = "${response.body()!!.login} \n ${response.body()!!.createdAt}"
                }
            } else {
                Toast.makeText(applicationContext, "Error ${response.code()}", Toast.LENGTH_SHORT).show()
            }
        }

        Toast.makeText(applicationContext, "The above network call in coroutine is not blocking this toast", Toast.LENGTH_LONG).show()

    }

}

The coroutine part in the above should be in a ViewModel or repository class, they are here only because the purpose of this post is to demonstrate using coroutine with retrofit. The coroutine in the following code is everything in the launch block. Everything in this launch block are executed sequentially, each line is executed one after another, if one of the lines is taking it’s sweet time to do its things, the next line will have to wait until the previous line finishes. However, everything outside of this launch block won’t be waiting for the anything in the launch block(coroutine).

Coroutine is similar to spin up a thread and run it in the background, but coroutine is a lot more light-weight than thread, you can spin up millions of coroutine, and your program will still run fine, but a million of thread will likely make it run out of memory.

The above network task can also be done using Rxjava. With RxJava, we will have to make sure to subscribe the observable on an background thread, and observe the result on the main thread, it will involve some callbacks, and the code will not be sequential. With coroutine, everything in the coroutine are executed sequentially, no nested callbacks.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <TextView
            android:id="@+id/tv_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text=""
            android:gravity="center"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

    <ProgressBar
            android:id="@+id/progress_circular"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Android multiple apps and modules in one project demo

 1. Create a new project using the Android Studio create new project wizard. Name the project thanksgiving.

2. Create a new module and name it base, File -> New -> New Module -> Select Android Library. Create Greeting class to be used by the thanksgiving and christmas apps. Greeting

object Greeting {
    val thanksgivingGreeting = "Happy Thanksgiving, eat some turkey!"
    val christmasGreeting = "Merry Christmas, unwrap yourself a joyful Christmas!"
}

3. Create a new app and name it christmas, File -> New -> New Module -> Select Phone and Tablet module.

4. Open the activity_main.xml in the thanksgiving and christmas app, update it to the following.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/tv_greeting"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

5. Open the app gradle files for the thanksgiving and christmas app, add this line in the dependencies to include the base module.

    implementation project(':base')

6. Add this line in the onCreate function in the thanksgiving app, it uses the Greeting class from the base module to set the TextView string.

tv_greeting.text = Greeting.thanksgivingGreeting

7. Add this line in the onCreate function in the christmas app, it uses the Greeting class from the base module to set the TextView string.

tv_greeting.text = Greeting.christmasGreeting

With the above steps, we have created an Android project with one library module and two Android apps in one source directory. The thanksgiving and christmas apps are using the Greeting class from the base module to get greetings. Since this is only for demo purpose, so the base module here is super simple that it only gives greetings, but you can create a lot more functionalities in the base module and use them in other apps.

Android simple MVVM example

 This is a demonstration of using Android architectural component ViewModel for a simple MVVM design pattern. It will use RxJava 2, RxKotlin, RxAndroid, Retrofit 2, etc. Github api will be used as example for calling REST service with Retrofit 2. https://api.github.com/users/google

1. Include this at the top of the app gradle file for kotlin compiler.

apply plugin: 'kotlin-kapt'

2. Added these dependencies in the app gradle file.

  //Architecture component
  implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
  kapt 'androidx.lifecycle:lifecycle-compiler:2.0.0'


  //RxJava
  implementation "io.reactivex.rxjava2:rxandroid:2.0.1"
  implementation "io.reactivex.rxjava2:rxjava:2.1.3"
  implementation "io.reactivex.rxjava2:rxkotlin:2.1.0"

  // Retrofit 2
  implementation 'com.squareup.retrofit2:retrofit:2.5.0'
  implementation "com.squareup.retrofit2:adapter-rxjava2:2.5.0"
  implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
  implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
  implementation 'com.google.code.gson:gson:2.8.2'

3. Create utility classes for creating Retrofit rest service interface, repository and ViewModel factories.
RestUtil.kt

class RestUtil private constructor() {
    private val API_BASE_URL = "https://api.github.com/"
    val retrofit: Retrofit

    init {
        val interceptor = HttpLoggingInterceptor()
        interceptor.level = HttpLoggingInterceptor.Level.BODY
        val httpClient = OkHttpClient.Builder().addInterceptor(interceptor).build()

        val builder = Retrofit.Builder()
            .baseUrl(API_BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())

        retrofit = builder.client(httpClient).build()
    }

    companion object {
        private var self: RestUtil? = null

        val instance: RestUtil
            get() {
                if (self == null) {
                    synchronized(RestUtil::class.java) {
                        if (self == null) {
                            self =
                                    RestUtil()
                        }
                    }
                }
                return self!!
            }
    }

}

RepositoryFactory.kt

object RepositoryFactory {

    fun createGithubRepository() : GithubRespository {
        val githubApi = RestUtil.instance.retrofit.create(GithubApi::class.java)
        return GithubRespository(githubApi)
    }

}

ViewModelFactory.kt

class ViewModelFactory(private val githubRespository: GithubRespository) : ViewModelProvider.NewInstanceFactory() {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MainActivityViewModel(githubRespository) as T
    }

}

4. Create data model, retrofit api interface, and repository.
GithubAccount.kt

data class GithubAccount(
    @SerializedName("login") var login : String = "",
    @SerializedName("id") var id : Int = 0,
    @SerializedName("created_at") var createdAt : String = "",
    @SerializedName("updated_at") var updatedAt : String = "")

GithubApi.kt

interface GithubApi {
    @GET("/users/{username}")
    fun getGithubAccount(@Path("username") username: String): Single<Response<GithubAccount>>
}

GithubRespository.kt

class GithubRespository(val githubApi: GithubApi) {

    fun fetchGithubAccount(name : String) : Observable<GithubAccount> {
        return Observable.create { emitter ->

            githubApi.getGithubAccount(name)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe( {
                    if (it.body() != null) {
                        emitter.onNext(it.body()!!)
                    }
                }, {
                    it.printStackTrace()
                })

        }
    }

}

5. Create the ViewModel which uses repository to get data and emit the data to the view through LiveData.

class MainActivityViewModel(val githubRespository: GithubRespository) : ViewModel() {

    private val _githubAccount : MutableLiveData<GithubAccount> = MutableLiveData()
    val githubAccount : LiveData<GithubAccount> = _githubAccount

    fun getGithubAccount(name : String) {
        githubRespository
            .fetchGithubAccount(name)
            .subscribe {
                _githubAccount.postValue(it)
            }
    }

}

6. Finally using the ViewModel in the View class, MainActivity.kt

class MainActivity : AppCompatActivity() {

    private lateinit var mainActivityViewModel : MainActivityViewModel

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

        mainActivityViewModel = ViewModelProviders.of(this, ViewModelFactory(RepositoryFactory.createGithubRepository())).get(MainActivityViewModel::class.java)

        mainActivityViewModel.githubAccount.observe(this, Observer {
            tv_content.text = it.toString()
        })


        mainActivityViewModel.getGithubAccount("google")
    }
}

7. Make sure add the INTERNET permission in the manifest file.

<uses-permission android:name="android.permission.INTERNET"/>

8. The layout file for the MainActivity, activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.MainActivity">

    <TextView
            android:id="@+id/tv_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text=""
            android:gravity="center"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Android share intent with html text

 The following will launch a bottom sheet with a list of apps and let you pick an app to share the content. For example, if you picked an email app, it will populate the subject and body in that email app. The example text here contains html tags, by setting the type to “text/html”, and parse the html content using HtmlCompat.fromHtml, it will renders the html in the email app or any other app that can handle html contents.

val shareIntent = Intent(android.content.Intent.ACTION_SEND)
shareIntent.type = "text/html"
shareIntent.putExtra(Intent.EXTRA_SUBJECT, "Android Notification")
shareIntent.putExtra(Intent.EXTRA_TEXT, HtmlCompat.fromHtml("<h1>Title</h1><p>This is a body</p>", HtmlCompat.FROM_HTML_MODE_LEGACY))
startActivity(Intent.createChooser(shareIntent, "Share with:"))

How to extract filename from Uri?

Now, we can extract filename with and without extension :) You will convert your bitmap to uri and get the real path of your file. Now w...