fbpx
Android widgets - Advanced – With Kotlin Flow, Room and Hilt

Android widgets – Update using Kotlin Flow, Room and Hilt

Reading Time: 8 minutes

In this Android widgets tutorial we are going to learn how we can update the Widget with Kotlin Flow, Room and Hilt. As an example we are going to extend a ToDo application (which already uses Room) by Kotlin Flow and Hilt to update the data of the Widget with the first item of our Todo list.

Basics tutorial

This tutorial assumes that you’re familiar with the basics of Android widgets. If you aren’t then please review our free Android widgets – Basics tutorial.

Advanced tutorial

If you think, that the basic tutorial wasn’t enough, then check out our advanced tutorial as well. In this tutorial you will learn
how you can make a

      • configuration screen,
      • update the widget
        • by itself and
        • from the app using Service.

You can find the advanced tutorial under the link: Android widgets – Advanced

The sample app

In this tutorial we are going to extend the example app of the Room basics tutorial. This tutorial teach you the basics of Room. How you can implement it and how does it work.

You can find the Room basics tutorial here: Android Room basics

GitHub

If you don’t want to do the tutorial, then you can download the starter app for this tutorial from GitHub using the below link:

GitHub – Room demo app

Be careful to download the viewmodel branch.

If you download the app, then in Android Studio open it by clicking on the

File -> New -> Import Project

In the popup window find the project’s folder or the build.gradle file.Β 

If the app is open, then check out the source files to get familiar with it, if you haven’t done the Room basics tutorial.

Step 1 – Add a dependency

This project already contains the needed dependencies for Kotlin Flow and Room, but not for Dagger Hilt.

Hilt is a dependency injection library for Android that reduces the boilerplate of doing manual dependency injection in your project.

Hilt provides a standard way to use DI in your application by providing containers for every Android class in your project and managing their lifecycles automatically. Hilt is built on top of the popular DI library Dagger to benefit from the compile-time correctness, runtime performance, scalability, and Android Studio support that Dagger provides.

Source: Dependency injection with Hilt

We can add the dependency for Hilt to the app in the App Build.gradle. So, open it from the left Project pane.

Next, paste the below implementation into the dependencies {} section.

implementation “com.google.dagger:hilt-android:$hilt_version
kapt “com.google.dagger:hilt-android-compiler:$hilt_version
kapt “androidx.hilt:hilt-compiler:$hilt_androidx_version

Then, add the below line to the top of App Build.gradle file.

apply plugin: ‘kotlin-kapt’

Thenafter we have to add one more line to the app, but in this case to the Project Build.gradle file, what you can find also in the Gradle Scripts.

Paste the below implementation into the dependencies {} section.

classpath “com.google.dagger:hilt-android-gradle-plugin:$hilt_version

The last step is to add the versions for the dependencies. So paste the below line directly to the top of the buildscript{} section.

ext.hilt_androidx_version = “1.0.0-alpha02”
ext.hilt_version = “2.28.3-alpha”

Finally, click on the Sync now button, what you can find in Android Studio at the top left corner.

Step 2 – Add the Widget

Our next task will be to create the widget. You can create it manually, or you can add it using Android Studio. In this tutorial we are going to select the second option. In this case Android Studio is going to prepare for us the needed files.

First we gonna create a new package, which will contain the file for the widget. So, with the right mouse button click on the main source set (where you can find the MainAcitvity.kt file as well). Then, from the quick menu select New, then the Package option and name it as “widget”.

Then again click with the right mouse button on the widget package, from the quick menu select New, then the Widget option. From the submenu choose the App Widget option.

Name the widget as “AppWidget”.

Our widget gonna be 4 cells wide, and 1 cell high.Β 

For this sample app we don’t need to create a Configuration screen, so leave unchecked the CheckBox.

Now, click on the Finish button.

Step 3 – Widget's layout

We won’t go into the details of the layout for the Widget, because as we have talked about earlier, it will be very simple. So just copy and paste the below xml lines into the app_widget.xml file, what you can find in the res->layout folder.


app_widget.xml

The shape of the widget

Our widget will get rounded corners and a bit dirty white background. For this we are going to create a new xml file. For this, click with the right mouse button on the drawable folder, then select the Drawable resource file option. In the popup window name the new file as “shape_roundedcorners_white”.

Then replace the xml code with the below one.


Shape of the widget

Step 4 – Implement Hilt

@HiltAndroidApp

In this step we are going to implement Hilt. So for the first step, open the MyApp::class, which is in the root of the main source set. Then annotate the class by @HiltAndroidApp.


@AndroidEntryPoint

The @AndroidEntryPoint annotation triggers Hilt’s code generation, including a base class for our application that serves as the application-level dependency container.

@RoomDatabaseModule

Next, we are going to create the modul for the ToDoRoomDatabase::class and ToDoDao::class into a new package, called “di”. So create it in the main source set as we have done it in case of the widget package.

A Hilt module is a class that is annotated with @Module. It informs Hilt how to provide instances of certain types. We must annotate Hilt modules with @InstallIn to tell Hilt which Android class each module will be used or installed in.

Source: Dependency injection with Hilt

Then create a new Kotlin file in the di package and name it as “RoomDatabaseModule”.

The below code into this new Kotlin file.


RoomDatabaseModule

      • @Singleton: Defines the instance as an application wide available singleton instance.
      • @Provides: Because we don’t direcly own the Room database, we have to annotate with @Provides. In this way we tell Hilt how to provide instances of this type.

@AndroidEntryPoint

The next step of the implementation is to annotate the AppWidget::class as well with
@AndroidEntryPoint.

Using the @AndroidEntryPoint annotation Hilt can provide dependencies to other Android classes. In our case we are going to use the dependency of the ToDoDatabaseΒ inside of the AppWidget::class. We are going to talk about this later.

@Inject DAO

The last step is to extend the constructor of the ToDoRepository::class to let Hilt inject the ToDoDAO::class. So, open it from the repository package, and modify the header of the class ot the below one.


@Inject constructor

That’s it, the implementation of Hilt is finished. 😎

Step 5 – Add Flow to DAO

So, finally, Flow. πŸ˜€ What is this exactly? You can ask.

Kotlin Flow is a new stream processing API, which is developed by JetBrains, the owner of the Kotlin langauge. It’s an implementation of the Reactive Stream specification, an initiative whose goal is to provide a standard for asynchronous stream processing. Jetbrains built Kotlin Flow on top of Kotlin Coroutines.

Kotlin Flow helps us to transform data in a complex multithreaded way with few lines of code.

After a short introduction, we will continue the implementation of this sample app. So, open the ToDoDAO::class from the repository package, and add to it the below function.


getFirstToDoItem()

Check out this method. Using the @Query annotations of the Room library we can define custom SQL query from the database. In our case we will get the todo task which has the toDoId 1.

The return type will be Flow<ToDo>.

Step 6 – Extend the repository

Next, we are going to add a call to the above getFirstToDoItem() method into the ToDoRepository::class. So, open it. You can find it in the repository package.


getFirstToDoItem

As you can see, in the case of the repository it is just a single line of code.

Step 7 – Update the AppWidget::class

Finally it’s time to get the data from Room and update the widget using Kotlin Flow.

Member variables

First, we have to create a Job and a CoroutineScope member variable for the AppWidget::class. So open the AppWidget.kt file from the widget package.

Using Job we can control the lifecycle of the coroutine. Because of this, later on, when we remove the instance of the widget, we have to cancel this Job as well.

All coroutines run inside a CoroutineScope and it takes a CoroutinesContext as a parameter. A CoroutinesContext is a set of elements, to define the threading policy, exception handler, control the lifetime of the coroutine, and so on. We can use plus operator to combine the elements of CoroutinesContext. There are 3 important CoroutinesContext:

      • Dispatchers:Defines which thread runs the coroutine.
      • CoroutineExceptionHandler: Handles uncaught exceptions.
      • Job

Dispatchers

      • Dispatchers.Default: This is the standard builder in case if neither the dipatcher nor the ContinuationInterceptor is specified in their context.
      • Dispatchers.IO: This dispatcher is optimized to perform disk or network I/O outside of the main thread.
      • Dispatchers.Main: If we would like to run a coroutine on the main Android thread, then use this one. This should be used only for interacting with the UI and performing quick work.
      • Dispatchers.Unconfined: A coroutine dispatcher that is not confined to any specific thread.


Member variables for Flow

The third member variable will be a field injected instance of the ToDoRepository::class. Using the @Inject Hilt annotation for the variable we can obtain dependency from a component.


Member variables for the ToDoRepository

Launch the Flow

Next, we are going to start using the above created member variables. First, we are going to launch the CoroutineScope, thenafter we can start collecting the ToDo item using Kotlin Flow. We are going to do that inside of the onReceive() method of the AppWidget::class.

The onReceive() method is called for every broadcast and before all callback methods, like onUpdate(), onEnabled(). It dispatch calls to the various other methods on AppWidgetProvider.


onReceive()

Using the collect() terminal operator we can start collecting the ToDo item. It means, it will emit the item in every case when it changes. Since collect() is a suspending function, it can only be called from a coroutine or another suspending function. This is why you wrap the code with CoroutineScope.

Below lines go into the body of the collect() terminal operator.


Update all instances of the widget

First we have to get all the ids of the widget’s instances, because it can happen, thet the user has created more instances. In our case all of them should have the same data. So, if the _toDo fetched variable is not null, then we will iterate through the ids and update the widget with the updateAppWidget().

Currently there we have some errors, because we haven’t updated the updateAppWidget() method yet.

Remove onUpdate()

For this app we won’t use the onUpdate() method, so simply just remove it from the AppWidget::class.

updateAppWidget()

This method could be familiar if you have done the Advanced Widgets tutorial. In this method we update the views of the widget using the parameters of the method. We won’t go into the details. So, just replace the whole updateAppWidget() method with the below one.


onReceive()

getPendingIntentActivity()

Now we have an error, because we haven’t provided the getPendingIntentActivity() method yet. This method will create a PendingIntent.


getPendingIntentActivity()

Cancel the Job

The last thing before we run the app is to cancel the Job, when we remove the widget’s instance. So, add the below line to the onDisabled() method.

job.cancel()

Run the app

Finally, run the app. It should work as you can see in the below short video.

In our example I have extended the app with a simple preview image, what you can find in the GitHub repository.

GitHub

The source code is available on GitHub, check it out and download it using the below link.

GitHub

Questions

I hope the description was understandable and clear. But, if you have still questions, then leave me comments below! πŸ˜‰

Have a nice a day! πŸ™‚

Click to rate this post!
[Total: 0 Average: 0]

Follow and like us:

Leave a comment

stay informed!

Subscribe to receive exclusive content and notifications