Today I had a minor epiphany. I was working on a unit test on a class method, and had a rather annoying time with it because the class (a ViewModel) uses an instance of a different class (a repository) to do things. There is a coroutine involved, so things got a bit hairy until I watched a tutorial video that essentially suggested a dependency required for testing to be added as parameter for the class’s constructor.
This required me to modify the actual class, and when I was done I just realized what this is about. It’s dependency injection!
Sure, I’ve written about it before (in an attempt to understand it), but it’s only when I’m actively working on adding unit tests that I experienced the actual value of D.I. Pretty neat!
In the spaced repetition app I’m building, I wanted to allow people to pick a time for the study reminder notifications to show up on. There is a built-in AndroidX Preference Library that is meant to help with this, however I found that there’s no built-in way to display a TimePicker dialog as part of the preference option. Some tinkering are still required, and here’s the solution that works for me.
Concept
Before heading to the code, here’s a quick recap of what will be needed. Think of them as the building blocks that have to connect together.
A class that derives from DialogPreference. This part is for managing the Preference aspect of the dialog preference to be displayed, such as its message, buttons, as well as for saving and retrieving the value selected. The library has some built-in dialog preferences (EditTextPreference, ListPreference, MultiSelectListPreference), but no such thing as TimePickerPreference, so we’ll have to make ourselves here.
A class that derives from PreferenceDialogFragmentCompat. This part is for managing the visual aspect of the dialog preference to be displayed. This is where we will actually create and display the TimePicker, for instance.
XML layout for the main preference page. This is used to for the visual aspect of the preferences page that’s shown to the user.
Lastly, a fragment that derives from PreferenceFragmentCompat, which essentially is a special type of fragment that’s meant to be used for displaying the preferences page based on the XML layout, and for wrangling various display options related to it.
1. TimePickerPreference
First thing we want to make is the TimePickerPreference. Here’s how my code looks like:
packageblack.old.spacedrepetitionowl
importandroid.content.Context
importandroid.util.AttributeSet
importandroidx.preference.DialogPreference
// This class is used in our preference where user can pick a time for notifications to appear.
// Specifically, this class is responsible for saving/retrieving preference data.
Above, I override onSetInitialValue() so that I can fill the preference’s summary with the actual time. The idea is to allow people to see the currently selected time, which should look like this:
A preference with the 10:30 value written in the summary area of the preference.
With custom dialog preference like this, I found that the summary area is what’s commonly used to show the current value of that preference.
The two functions persistMinutesFromMidnight() and getPersistedMinutesFromMidnight() are to be used for saving and retrieving the value related to this preference.
2. TimePickerPreferenceDialog
After the preference aspect is created, now we set up the actual dialog. Here’s the code that I have:
Inside onCreateDialogView(), we generate the TimePicker to be displayed.
Inside onBindDialogView(), we grab the current hour/minute preference value, and set that value into the TimePicker. The goal is to make it so that when someone first opens the TimePicker dialog, the clock already shows the current value instead of starting from zero. (preference as TimepickerPreference) is needed here because without it, the compiler does not now that the current preference is a custom TimePickerPreference, and hence will not be able to find the function we need.
Inside onDialogClosed(), we save the value selected by the user after clicking the positive button.
The interesting part might be <black.old.spacedrepetitionowl.TimepickerPreference> which indeed is the class we created on step 1. During run-time, the android:summary part is programmatically modified and filled in both within TimePickerPreference (on initial opening of the preference screen), and TimePickerDialogPreference (after user picked a time).
4. PreferenceFragment
This is the regular fragment that we’ll use to display the preference screen. Here’s my file:
First, we’re pulling the XML layout inside onCreatePreferences().
Next, since we want to display our own custom dialog preference, we need to override onDisplayPreferenceDialog(). Here, we specifically want to check if the currently selected preference is our custom preference, TimePickerPreference. If it is, we instantiate a new TimePickerPreferenceDialog and display it. If it’s a different type of dialog preference (let’s say it’s one of the three built-in dialog preferences mentioned earlier), we let it be handled automatically by the superclass.
And that’s it! This is just a small example but hopefully is a good starting point if you want to create a different kind of custom dialog preference 🙂
The steps are relatively simple. Suppose that we want to send data from Fragment A to Fragment B:
Create a ViewModel
Have Fragment A and Fragment B share that same ViewModel.
Create a LiveData inside the ViewModel to contain the data that’s to be passed from A to B.
Assign or update value to the LiveData object inside Fragment A, usually with the help of a public method inside the ViewModel to assign or update the value.
Have Fragment B observe the LiveData inside it onCreate() , and make use of the data there.
Jetpack Compose is Google’s latest toolkit for building UI, at the moment generally aimed for Android development. Below is a list of what I think is a good, comprehensive approach on how to learn about it. At the moment, Jetpack Compose is under active development and currently released in Alpha version, so this list will continuously evolve with it as well.
Understanding Jetpack Compose — Part 1 of 2 by Leland Richardson, one of Google’s engineers developing Jetpack Compose. The post helps explain the “why” of Jetpack Compose. What challenges is it trying to address, and how.
Thinking in Compose. Also helps explain why the declarative UI model as used by Jetpack Compose is beneficial.
Official documentation
Layouts in Compose. Explains how layouts can be created using Jetpack Compose and shows the built-in components that come with it.
Theming in Compose. Explains how to modify the look-and-feel of an app made with Jetpack Compose.
Interoperability. Explains how to add, use, and combine Jetpack Compose with existing apps that already use the view-based UI model.
Sample apps
https://github.com/android/compose-samples . Official samples from the Android repository. Has multiple different sample apps in it, with the Readme file helpfully explaining what concepts are being explored and used in each app.
Also, Elsewhere
React. A declarative UI library written in JavaScript, mostly used on web development.
SwiftUI. A declarative UI library for development within the Apple platform.
Technically the video above is plenty enough to introduce Hilt, so at this point I could have ended this article 😆
Nevertheless, I’ll try to make a shorter summary below with the hope that it will make it simpler to understand from a beginner’s perspective.
Essentialy, Hilt is meant to help with adding D.I. in our application. There’s some automated processes that it does so that we don’t have to manually add dependency injection.
Why should we have Hilt when Dagger already exists? From what I understand, the main idea is to simplify. And Hilt itself is made with Dagger at its base. According to Dagger’s site:
Hilt provides a standard way to incorporate Dagger dependency injection into an Android application.
The goals of Hilt are:
– To simplify Dagger-related infrastructure for Android apps.
– To create a standard set of components and scopes to ease setup, readability/understanding, and code sharing between apps.
– To provide an easy way to provision different bindings to various build types (e.g. testing, debug, or release).
For those who haven’t went deep with Dagger, I think learning Hilt instead will be more efficient and pragmatic (unless if in practice you will be dealing with existing codebases that already use Dagger.)
If we look at the new navigation menu hierarchy on the Dependency Injection page on the Android training page, we can see that Hilt is shown earlier than Dagger, giving it a bit more perceived importance than Dagger.
Basic Concepts for D.I. Frameworks
If you haven’t learned about Dependency Injection whatsoever, please open and read this article first, before continuing here.
The common technique in creating D.I. is by using the idea of container. When creating D.I. manually, container will be a class that is supposed to be the central place where all dependencies are generated. An example from Android training:
// Container of objects shared across the whole app
class AppContainer {
// Since you want to expose userRepository out of the container, you need to satisfy
// its dependencies as you did before
private val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
private val remoteDataSource = UserRemoteDataSource(retrofit)
private val localDataSource = UserLocalDataSource()
// userRepository is not private; it'll be exposed
val userRepository = UserRepository(localDataSource, remoteDataSource)
}
Next, the class is instantiated within Application(), which acts as the entry point of the app, so that subsequent components like fragments or activites can use the container to get their dependencies.
At its core, a D.I. framework like Hilt is supposed to make the process above easier. It will automatically generate the container class for us, as well as help inject the dependencies (hence why it’s called Dependency Injection) into the components that need them.
Basic Concepts for Hilt
Roughly speaking, first we need to teach Hilt a few things to it can automate things for us. As commonly used in Android programming, we use annotation for this.
First, the @HiltAndroidApp annotation is used to teach Hilt where the Application()instance is in our app:
@HiltAndroidApp
class ExampleApplication : Application() { ... }
The second annotation that’s needed is @AndroidEntryPoint. It is used to signify components that will need some injections:
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() { ... }
The third important annotation is @Inject. It has two roles. First, it is used to “tag” dependencies that will eventually be injected into components:
class AnalyticsAdapter @Inject constructor(
 private val service: AnalyticsService
) { ... }
Second, it is also used within components to actually do the injection:
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter
...
}
Going Forward with Hilt
The three annotations above are the most basic things we need to get started with Hilt. Going further, there are different cases that will require further setup with Hilt. Cases such as:
D.I. related with interface
D.I. for dependencies with third-party or external libraries like Retrofit or Room database
D.I. with more than one implementation for the same type to be injected.
Saya sudah beberapa kali menulis soal dependency injection (D.I.) di blog ini. Masih yang basic-basic saja, pertama soal memahami Dagger dan kedua soal cara sederhana memahami D.I dan mengapa dia dibutuhkan. Berita terbaru, ada library baru dari Google untuk membantu mempermudah mengaplikasikan D.I. di app kita. Namanya Hilt:
Sebenarnya video di atas sudah sangat cukup untuk memperkenalkan Hilt, jadi sampai di sini artikel ini sebenarnya bisa ditutup 😆
Tetapi berikut saya akan mengikhtisarkan sedikit dengan harapan agar dia lebih mudah dipahami lagi.
Jadi Hilt pada intinya difungsikan untuk membantu proses menambahkan D.I. di aplikasi kita. Ada proses otomatisasi di dalamnya agar kita tidak perlu melakukan D.I. secara manual.
Mengapa harus ada Hilt kalau sudah ada Dagger? Di sini sepertinya kata kuncinya adalah penyederhanaan. Hilt sendiri dibuat menggunakan Dagger sebagai basisnya. Menurut situs Dagger:
Hilt provides a standard way to incorporate Dagger dependency injection into an Android application.
The goals of Hilt are:
To simplify Dagger-related infrastructure for Android apps.
To create a standard set of components and scopes to ease setup, readability/understanding, and code sharing between apps.
To provide an easy way to provision different bindings to various build types (e.g. testing, debug, or release).
Hilt menyediakan cara standar untuk menambahkan Dagger dependency injection ke dalam sebuah aplikasi Android.
Tujuan Hilt antara lain:
Menyederhanakan infrastuktur yang terkait dengan Dagger dalam aplikasi Android
Membuat set komponen dan scopes yang standar untuk memudahkan dalam setup, membaca/memahami kode, dan berbagi kode dalam banyak aplikasi.
Menyediakan cara yang mudah untuk membuat binding yang berbeda-beda untuk build types yang berbeda (misalkan unutk testing, debug, atau release).
Bagi yang belum terlalu mendalami Dagger, saya rasa mempelajari Hilt akan lebih efisien dan pragmatis (kecuali jika pada prakteknya harus berhadapan dengan kode yang dari awal sudah menggunakan Dagger).
Jika kita melihat dari hierarki menu di halaman Dependency Injection di Android training milik Google, Hilt ditampilkan lebih awal dari Dagger, yang sepertinya secara halus menunjukkan kalau Google ingin mendorong orang-orang untuk memakai Hilt.
Teknik umum dalam membuat D.I. adalah dengan menggunakan konsep kontainer. Apabila membuat D.I. secara manual, kontainer adalah class yang bertugas menjadi tempat semua dependensi dibuat. Contoh dari Android:
// Container of objects shared across the whole app
class AppContainer {
// Since you want to expose userRepository out of the container, you need to satisfy
// its dependencies as you did before
private val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
private val remoteDataSource = UserRemoteDataSource(retrofit)
private val localDataSource = UserLocalDataSource()
// userRepository is not private; it'll be exposed
val userRepository = UserRepository(localDataSource, remoteDataSource)
}
Kemudian, class ini dibuat instance-nya di dalam Application(), yang bertindak sebagai pintu masuk aplikasi, sehingga komponen-komponen berikutnya seperti fragments atau activities bisa menggunakan kontainer tersebut untuk membuat dependensi.
Sebuah D.I. framework seperti Hilt pada intinya bertugas membuat proses di atas lebih mudah. Dia bisa membuatkan class kontainer untuk kita, sekaligus “menyuntikkan” dependensi (dari sini istilah Injection di D.I. berasal) ke dalam komponen-komponen yang membutuhkannya.
Konsep dasar Hilt
Secara kasar, kita perlu memberi tahu Hilt bagaimana caranya agar dia bisa membuatkan kontainer dan menginjeksi dependensi secara otomatis. Caranya, seperti yang umum digunakan di pemrograman Android, adalah dengan menggunakan anotasi/annotation.
Pertama, anotasi @HiltAndroidApp untuk memberi tahu Hilt di mana Application()instance di aplikasi kita:
@HiltAndroidApp
class ExampleApplication : Application() { ... }
Anotasi kedua yang diperlukan adalah @AndroidEntryPoint. Ini untuk memberi tanda kepada komponen yang membutuhkan injeksi:
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() { ... }
Anotasi ketiga yang juga penting adalah @Inject. Dia punya dua tugas. Pertama, untuk memberi tanda kepada dependensi-dependensi yang nantinya perlu diinjeksi ke dalam sebuah komponen:
class AnalyticsAdapter @Inject constructor(
 private val service: AnalyticsService
) { ... }
Kedua, dia juga dipakai di dalam komponen di mana injeksi harus dilakukan:
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter
...
}
Lebih Lanjut dengan Hilt
Tiga jenis anotasi di atas pada dasarnya sudah cukup untuk memulai menggunakan Hilt. Lebih lanjut, ada kasus-kasus lain yang membutuhkan setup lebih banyak untuk dikerjakan dengan Hilt. Kasus-kasusnya seperti:
D.I. terkait dengan interface
D.I. untuk dependensi yang berasal dari third-party atau library eksternal seperti Retrofit atau Room database
D.I. dengan lebih dari satu implementasi untuk satu type yang sama
On small screens like on mobile (setting aside how humongous smartphones tend to be nowadays), a very common pattern is to use a modal navigation drawer to allow users to navigate between one top-level destinations in the app. You’ve seen them: it’s usually the three horizontal lines on top left of an app, that can be tapped to show and hide a side drawer with a list of menu items inside of it.
Despite being a common design pattern, when I tried to learn how implement it in conjunction with the Jetpack Navigation Component, the available resources can be quite difficult to understand. They’re out there, but in my experience they’re a bit scattered and I had a hard time creating the right mental model in my mind on how to put things together.
This article is my attempt to sum things up. For things to make sense, you will need some familiarity with basic Android development and the Navigation Component itself.
This article is more of a quick note about what’s involved when your Android app needs to create a notification and schedule it to appear at some time in the future. There’s a lot of articles out there that goes in details about the implementation. Think of this article as more of a summary.
With my current app, the main data is Subjects, where each Subject have multiple Reminders tied to it. For this purpose, I have a couple of tables in the database, which in its simplified form can be seen as below:
Subject table with:
Subject ID
Subject title
Reminder table with:
Reminder ID
Reminder timestamp
Subject ID
I’m using Room for database and LiveData to pull data from the tables. The data then gets supplied to a RecyclerView, where each RecyclerView item will display a Subject with its Reminders.
RecyclerView where each row is a Subject and its four Reminder dates.
The tricky part is that, since there are two tables, I have two separate methods returning LiveData object from each table. Most tutorials out there assume the use of only one LiveData to be observed by the RecyclerView, so it can get confusing when there’s two LiveData at once.
I found a smart solution on Stack Overflow that makes use of MediatorLiveData and Kotlin’s Pair to make the whole thing work. This article is an attempt to expand on that solution by writing down my understanding of it.
This article assumes familiarity with Android concepts for RecyclerView, Room, ViewModel, and LiveData.
Create a “combination” MediatorLiveData class
The first thing to do is to create a class that extends MediatorLiveData. The interesting part is that we need to use Pair<List<Subject>, List<Reminder>> as MediatorLiveData’s type.
After that, we simply construct the class by sending it LiveData<List<Subject>> and LiveData<List<Reminder>> as constructor parameters, then put those two inside a Pair, and finally set the Pair as the MediatorLiveData object’s value.
importandroidx.lifecycle.LiveData
importandroidx.lifecycle.MediatorLiveData
// This class ia a MediatorLiveData created since I have two different sources of data:
// – subjects table
// – reminders table
// Since they then become two separate LiveData, and I need both to fill in the data
// on the main RecyclerView, then we're using a MediatorLiveData to grab both LiveData
// and add them as sources, so that we can notify observers when any of the data gets
Example of a MediatorLiveData class to handle two LiveDatas at once.
That is pretty much where the magic lies. The potentially tricky part is the addSource method and the value variable, both of which are built-in features of the MediatorLiveData.
Inside ViewModel
Inside the ViewModel, we call the repository’s methods for pulling LiveData from each table, then we return a “combination” object as we describe in the class above, and put in the LiveData as its parameters.
Put in the two List<> as constructor parameters in RecyclerViewAdapter
Observe the MediatorLiveData
Finally, inside the fragment or activity that has the RecyclerView, we observe the returned MediatorLiveData from the ViewModel. When there’s any change, the observer gives a Pair containing a List<Subject> and a List<Reminder>. We can then put those two as the parameter of the adapter.
Observe the MediatorLiveData given by ViewModel, and update RecyclerViewAdapter accordingly.
Note how the Observer pattern uses a lambda with subjectsAndRemindersPair as the parameter. That is essentially the type of the MediatorLiveData in question, and by default Kotlin call that parameter it. I renamed it to subjectsAndRemindersPair to make it easier to understand.
This article assumes familiarity with creating a RecyclerView using a single ViewHolder for each item, which is the most common use case.
Here are the possible cases where a RecyclerView might use an adapter with multiple ViewHolders instead:
Case One
Have one RecyclerView to display different layouts for different items at the same time, based on certain conditions. Just as an example, here I have a RecyclerView that uses two different layouts for each item based on odd/even position.
Have one RecyclerView to display the same layout for each item. However, based on user action on certain conditions (such as during sorting or filtering action), the same RecyclerView will display the same data using a different layout. As an example, here’s a RecyclerView that uses just one layout for each item. Use your own imagination for an example where the RecyclerView at a different time uses a different layout instead.
Here’s the general method. I’m only listing areas that differ from the usual case of RecyclerView with just one ViewHolder:
We now have to create the multiple ViewHolder classes first. I prefer them to be inner classes of the custom RecyclerView.Adapter class. Pretty self-explanatory, the only possible gotcha/oddity here is that inside the class’s constructor, we create variables that directly access specific views inside a certain layout, without the class knowing about which layout it is related to.
The RecyclerView adapter now must extend the more generic RecyclerView.Adapter<RecyclerView.ViewHolder>class instead of using the single custom ViewHolder class as the generic type.
The RecyclerView now must override getItemViewType(), which is used to decide which ViewHolder class to be used on a certain item position in the RecyclerView.
The method onCreateViewHolder() must now return the more generic RecyclerView.ViewHolder
The method onBindViewHolder() must now use the more generic RecyclerView.ViewHolder object as its first parameter.
Case 2
Have a variable to determine which ViewHolder should be used by the RecylerView. This will usually be inside the fragment or activity.
Pass that variable as one of the parameters for constructing the RecyclerView’s adapter.
Inside the adapter, create the multiple ViewHolder classes first. I prefer them to be inner classes of the custom RecyclerView.Adapter class. Pretty self-explanatory, the only possible gotcha/oddity here is that inside the class’s constructor, we create variables that directly access specific views inside a certain layout, without the class knowing about which layout it is related to.
The RecyclerView adapter now must extend the more generic RecyclerView.Adapter<RecyclerView.ViewHolder>class instead of using the single custom ViewHolder class as the generic type.
The RecyclerView adapter now must override getItemViewType(), and check the variable from step 2 to determine which ViewHolder to use.
The method onCreateViewHolder() must now return the more generic RecyclerView.ViewHolder
The method onBindViewHolder() must now use the more generic RecyclerView.ViewHolder object as its first parameter.