Android widgets – Update using Kotlin Flow, Room and Dagger-Hilt
Tags: android, kotlin, hilt, flow
Published: 2020-09-05 13:54:53

## **Overview**
**In this Android tutorial I'm going to show you an approach which helps to update the app's widget realy easy using Kotlin Flow, Room and Dagger-Hilt.**

### **Introduction**
First of all, we have to talked in a few words about what does Koltin Flow, Room and Dagger-Hilt mean.

#### **Kotlin Flow**
Kotlin Flow is developed by JetBrains, the owner of the Kotlin language and it is a new stream processing API. It implements the Reactive Stream specification, and its goal is ot provide a standard for asynchronous stream processing. Kotlin Flow is build on top of Kotlin Coroutines.
Using Flow we handle streams of values, transform the data in a complex threaded way with only few lines of code.

#### **Room**
*The Room persistence library provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite.*

*The library helps you create a cache of your app's data on a device that's running your app. This cache, which serves as your app's single source of truth, allows users to view a consistent copy of key information within your app, regardless of whether users have an internet connection.*
[Source: Room Persistence Library](https://developer.android.com/topic/libraries/architecture/room?gclid=Cj0KCQjw7sz6BRDYARIsAPHzrNIuwKudcihbdyQ-syFtzFXjpTGKUX8nqeheNtD8i1kzrTS__1wlgBYaAlsNEALw_wcB&gclsrc=aw.ds)

#### **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](https://developer.android.com/training/dependency-injection/hilt-android)

## **The sample app**
[![Sample app](https://raw.githubusercontent.com/inspire-coding/WidgetWithFlowRoomHilt/finished_version/app/ThumbNail.png)](https://www.youtube.com/watch?v=tkpYq8OJNHM "Sample app")

**So, after a short indtroduction, let's start coding. :)**

## **Step 1 - Get the starter project**
For this tutorial we are going to use a starter project, which is a ToDo app. This app contains Room and RecyclerView to show the created ToDo items.

The tutorial for the starter project is available on Inspire Coding: [Room basics – Introduction](https://inspirecoding.app/room-basics-introduction/)

#### GitHub
If you don't want to do the starter tutorial, then you can get the starter project from GitHub as well: [GitHub](https://github.com/inspire-coding/RoomBasics/tree/viewmodel)

Clone or just download the project, and import it in Android Studio.

## **Step 2 - Add the widget**
The next step is to add the "widget" package to the main source set.
If the package is created, then create into it a new widget with the below detailes
- Width: 4 cells
- Height: 1 cell
- No Configuration Screen needed
- Language: Kotlin

## **Step 3 - Widget's layout**
The layout gonna be very simple.
- TextView for the title
- TextView for the due date
- TextView for the description
- ImageView for the priority

```
< RelativeLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	android:id="@+id/widget_layout"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:background="@drawable/shape_roundedcorners_white">

	<TextView
		android:id="@+id/tv_widget_title"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_marginStart="8dp"
		android:layout_marginTop="8dp"
		android:text="Task 1"
		android:textColor="@color/colorPrimary"
		android:textSize="14sp" />

	<TextView
		android:id="@+id/tv_widget_dueDate"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_below="@id/tv_widget_title"
		android:layout_marginStart="8dp"
		android:text="@string/duedate"
		android:textColor="@color/darkGrey"
		android:textSize="8sp" />

	<TextView
		android:id="@+id/tv_widget_description"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_below="@id/tv_widget_dueDate"
		android:layout_alignParentStart="true"
		android:layout_toStartOf="@id/iv_widget_priority"
		android:layout_marginStart="8dp"
		android:layout_marginEnd="8dp"
		android:text="@string/placeholder_text"
		android:textColor="@color/darkGrey"
		android:textSize="10sp" />

	<ImageView
		android:id="@+id/iv_widget_priority"
		android:layout_width="30dp"
		android:layout_height="30dp"
		android:layout_alignParentTop="true"
		android:layout_alignParentBottom="true"
		android:layout_alignParentEnd="true"
		android:layout_marginEnd="8dp"
		android:src="@drawable/prio_green" />

</ RelativeLayout>
```

#### **The shape of the widget**
Now create a new Drawable resource file with the name: shape_roundedcorners_white.xml and paste into it the below code.

```
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <solid
        android:color="@color/white"/>
    <corners
        android:radius="12dp"/>

</shape>
```

This will be the background of the widget.


## **Step 4 - Implement Hilt**
#### **@HiltAndroidApp**
Add the @HiltAndroidApp annotation to the MyApp::class

#### **@RoomDatabaseModule**
Then create a Hilt module into a new package called "di". This module will get the name of RoomDatabaseModule.
And the file looks like below.

```
@InstallIn(ApplicationComponent::class)
@Module
class RoomDatabaseModule
{
    @Singleton
    @Provides
    fun providesDatabase (application: Application) = ToDoRoomDatabase.getDatabase(application)

    @Singleton
    @Provides
    fun providesCurrentWeatherDao (database: ToDoRoomDatabase) = database.toDoDao()
}
```

#### **@AndroidEntryPoint**
Thenafter add the @AndroidEntryPoint annotation to the AppWidget::class.

#### **@Inject DAO**
Finally modify the ToDoRepository::class to use a constructor injection in the header of the class for the ToDoDAO.

```
class ToDoRepository @Inject constructor (private val toDoDao: ToDoDao)
```

## **Step 5 – Add Flow to DAO**
In this step we are going to implement the needed methods to get the todo item from Room which has the todoId of 1.
For this we are going to add the below method to the ToDoDAO::class.

```
@Query("SELECT * FROM ToDo WHERE toDoId = 1")
fun getFirstToDoItem() : Flow<ToDo>
```
Note that the method return Flow which wraps a ToDo type.

## **Step 6 – Extend the repository**
Then add the below method to the ToDoRepository::class.

```
val getFirstToDoItem : Flow<ToDo> = toDoDao.getFirstToDoItem()
```
The return value is the same like the same method in the ToDoDAO::class.

## **Step 7 – Update the AppWidget::class**
Thenafter we will add 3 member variables to the AppWidget::class.

```
private val job = SupervisorJob()
val coroutineScope = CoroutineScope(Dispatchers.IO + job)
@Inject lateinit var toDoRepository: ToDoRepository
```
Because Flow is a suspend function, we can call it only from CoroutineScope. That's why we need the Job and the CoroutineScope.
To get the item from Room is an IO operation, so we are going to add the Dispatchers.IO to the CoroutineScope.

Next, remove the onUpdate() and the onEnabled() methods. We don't need them.

Thenafter add the onReceive() method to the AppWidget::class.

```
override fun onReceive(context: Context, intent: Intent?)
{
	super.onReceive(context, intent)

	coroutineScope.launch {
		toDoRepository.getFirstToDoItem.collect { _toDo ->
			val appWidgetManager = AppWidgetManager.getInstance(context)
			val man = AppWidgetManager.getInstance(context)
			val ids = man.getAppWidgetIds(ComponentName(context, AppWidget::class.java))

			if (_toDo != null)
			{
				for (appWidgetId in ids)
				{
					updateAppWidget(
						context, appWidgetManager, appWidgetId,
						_toDo.title, _toDo.dueDate, _toDo.description, _toDo.priority
					)
				}
			}
		}
	}
}
```

Now you should have an error, because we haven't extended the updateAppWidget() method. So, replace it with the below one.

```
internal fun updateAppWidget(
    context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int,
    title: String?, dueDate: String?, description: String?, priority: String?)
{
    // Construct the RemoteViews object
    val views = RemoteViews(context.packageName, R.layout.app_widget)

    if (title != null) {
        views.setTextViewText(R.id.tv_widget_title, title)
    } else {
        views.setTextViewText(R.id.tv_widget_title, "")
    }
    if (dueDate != null) {
        views.setTextViewText(R.id.tv_widget_dueDate, dueDate)
    } else {
        views.setTextViewText(R.id.tv_widget_dueDate, "")
    }
    if (description != null) {
        views.setTextViewText(R.id.tv_widget_description, description)
    } else {
        views.setTextViewText(R.id.tv_widget_title, "")
    }
    if (description != null) {
        when (priority)
        {
            Prioirities.LOW.name -> views.setImageViewResource(R.id.iv_widget_priority, R.drawable.prio_green)
            Prioirities.MEDIUM.name -> views.setImageViewResource(R.id.iv_widget_priority, R.drawable.prio_orange)
            Prioirities.HIGH.name -> views.setImageViewResource(R.id.iv_widget_priority, R.drawable.prio_red)
        }
    }

    views.setOnClickPendingIntent(R.id.widget_layout,
        getPendingIntentActivity(context))

    // Instruct the widget manager to update the widget
    appWidgetManager.updateAppWidget(appWidgetId, views)
}
```

And one more error. It is there, because we haven't implemented the getPendingIntentActivity() method yet. This method will create for the views a PendingIntent to update the views if the widget's instances.
So, paste the below method at the end of the AppWidget.kt file.

```
private fun getPendingIntentActivity(context: Context): PendingIntent
{
    // Construct an Intent which is pointing this class.
    val intent = Intent(context, MainActivity::class.java)
    // And this time we are sending a broadcast with getBroadcast
    return PendingIntent.getActivity(context, 0, intent, 0)
}
```

So, we are almost done. One more thing left before we can test the app. When we delete an instance of the AppWidget, we have to cancel its Job as well. So add the below line to the onDisabled() method.

```
job.cancel()
```

## **Run the app**
Finally its time to run the app.

## **More Android tutorials**
If you would like to do more Android tutorials like this, then visit my website:
[![Inspire Coding](https://secureservercdn.net/160.153.137.218/yv6.cb0.myftpupload.com/wp-content/uploads/2019/11/cropped-Header-of-the-website.png)](https://inspirecoding.app/ "Inspire Coding")

## **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! 🙂