Kotlin Flow in Android with Example - GeeksforGeeks

16 Aug, 2021
Kotlin Flow is one of the latest addition to the Kotlin Coroutines. With Kotlin Flow we can handle streams of data asynchronously which is being executed sequentially. 
We will build a simple app that fetches some data from API and shows it on the screen. It’s a simple app demonstrating the Kotlin flow working. It will use MVVM architecture. 
Prerequisites:
Good knowledge of AndroidKnowledge of KotlinBasics of MVVM ArchitectureBasics of Retrofit LibraryBasics of Kotlin coroutinesBasics of View Binding
Good knowledge of Android
Knowledge of Kotlin
Basics of MVVM Architecture
Basics of Retrofit Library
Basics of Kotlin coroutines
Basics of View Binding
Step 1: Create a New Project
To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Note that select Kotlin as the programming language.
Step 2: Project Structure
We will be following some patterns to keep our files. Create folders and files according to this project structure. Uses will be explained later in this article.
Project structure
Step 3: Add required dependencies
Navigate to the Gradle Scripts > build.gradle(Module:app) and add the below dependency in the dependencies section.
// Retrofit dependency
implementation ‘com.squareup.retrofit2:retrofit:2.9.0’
// json convertor factory
implementation ‘com.squareup.retrofit2:converter-gson:2.1.0’
// Coroutines(includes kotlin flow)
implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0’
// lifecycle components
implementation ‘androidx.lifecycle:lifecycle-extensions:2.2.0’
implementation “androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1”
implementation “androidx.lifecycle:lifecycle-runtime-ktx:2.3.1”
Step 4: Working with the activity_main.xml
Navigate to the app > res > layout > activity_main.xml and add the below code to that file. Below is the code for the activity_main.xml file. 
XML
<?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"    android:padding="16dp"    tools:context=".presentation.MainActivity">     <androidx.constraintlayout.widget.Guideline        android:id="@+id/guideline2"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:orientation="vertical"        app:layout_constraintGuide_percent="0.3" />     <EditText        android:id="@+id/search_edit_text"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:autofillHints="Search Comments By Id"        android:inputType="numberSigned"        app:layout_constraintLeft_toLeftOf="parent"        app:layout_constraintRight_toLeftOf="@id/button"        app:layout_constraintTop_toTopOf="parent" />     <Button        android:id="@+id/button"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="Search"        app:layout_constraintRight_toRightOf="parent"        app:layout_constraintTop_toTopOf="parent" />     <ProgressBar        android:layout_width="match_parent"        android:id="@+id/progress_bar"        android:layout_height="wrap_content"        app:layout_constraintBottom_toTopOf="@+id/textView"        app:layout_constraintEnd_toEndOf="parent"        android:visibility="gone"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@id/search_edit_text" />     <TextView        android:id="@+id/textView"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginTop="20dp"        android:layout_marginEnd="20dp"        android:layout_marginRight="20dp"        android:text="Comment Id"        android:textStyle="bold"        app:layout_constraintEnd_toStartOf="@+id/guideline2"        app:layout_constraintHorizontal_bias="1.0"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@id/search_edit_text" />     <TextView        android:id="@+id/comment_id_textview"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginStart="20dp"        android:layout_marginLeft="20dp"        android:layout_marginTop="20dp"        android:textStyle="bold"        app:layout_constraintBottom_toBottomOf="@+id/textView"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintHorizontal_bias="0.0"        app:layout_constraintStart_toEndOf="@+id/guideline2"        app:layout_constraintTop_toBottomOf="@id/search_edit_text" />     <TextView        android:id="@+id/textView2"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginTop="20dp"        android:layout_marginEnd="20dp"        android:layout_marginRight="20dp"        android:text="Name"        android:textStyle="bold"        app:layout_constraintEnd_toStartOf="@+id/guideline2"        app:layout_constraintHorizontal_bias="1.0"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@id/comment_id_textview" />     <TextView        android:id="@+id/name_textview"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginStart="20dp"        android:layout_marginLeft="20dp"        android:textStyle="bold"        app:layout_constraintBottom_toBottomOf="@+id/textView2"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintHorizontal_bias="0.0"        app:layout_constraintStart_toEndOf="@+id/guideline2"        app:layout_constraintTop_toTopOf="@+id/textView2" />     <TextView        android:id="@+id/textView3"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginTop="20dp"        android:layout_marginEnd="20dp"        android:layout_marginRight="20dp"        android:text="Email"        android:textStyle="bold"        app:layout_constraintEnd_toStartOf="@+id/guideline2"        app:layout_constraintHorizontal_bias="1.0"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@id/name_textview" />     <TextView        android:id="@+id/email_textview"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginStart="20dp"        android:layout_marginLeft="20dp"        android:layout_marginTop="20dp"        android:textStyle="bold"        app:layout_constraintBottom_toBottomOf="@+id/textView3"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintHorizontal_bias="0.0"        app:layout_constraintStart_toEndOf="@+id/guideline2"        app:layout_constraintTop_toBottomOf="@id/name_textview" />     <TextView        android:id="@+id/textView4"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginTop="20dp"        android:layout_marginEnd="20dp"        android:layout_marginRight="20dp"        android:text="Comment"        android:textStyle="bold"        app:layout_constraintEnd_toStartOf="@+id/guideline2"        app:layout_constraintHorizontal_bias="1.0"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@id/email_textview" />     <TextView        android:id="@+id/comment_textview"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginStart="20dp"        android:layout_marginLeft="20dp"        android:layout_marginTop="20dp"        android:textStyle="bold"        app:layout_constraintBottom_toBottomOf="@+id/textView4"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintHorizontal_bias="0.0"        app:layout_constraintStart_toEndOf="@+id/guideline2"        app:layout_constraintTop_toBottomOf="@id/email_textview" /> </androidx.constraintlayout.widget.ConstraintLayout>
 
 
It UI looks like this:
 
 
Step 5: Working with the API
 
We will be using https://jsonplaceholder.typicode.com/comments API, It gives some JSON data when id is passed as a path. For example, https://jsonplaceholder.typicode.com/comments/2 gives JSON which contains some random data. We will be using this data and show it on the screen using kotlin flow. Open model > CommentModel and create a model class to parse data that is received from the API.
 
Example Response
 
We need to create a dataclass for this response. Add the following code in CommentModel
 
Kotlin
data class CommentModel(    val postId: Int?=null,    val id: Int?=null,    val email: String?=null,    val name:String?=null,     @SerializedName("body")    val comment: String?=null)
 
 
Creating API Interface
 
We need to create an API interface to call the API using a retrofit. Open network > ApiService and add the following code
 
Kotlin
import retrofit2.http.GETimport retrofit2.http.Path interface ApiService {    // Get method to call the api ,passing id as a path    @GET("/comments/{id}")    suspend fun getComments(@Path("id") id: Int): CommentModel}
 
 
Let’s add some helper classes to handle the loading or error state of API. Open network > CommentApiState. Refer to the comment in the code for an explanation.
 
Kotlin
// A helper class to handle statesdata class CommentApiState<out T>(val status: Status, val data: T?, val message: String?) {     companion object {         // In case of Success,set status as        // Success and data as the response        fun <T> success(data: T?): CommentApiState<T> {            return CommentApiState(Status.SUCCESS, data, null)        }         // In case of failure ,set state to Error ,        // add the error message,set data to null        fun <T> error(msg: String): CommentApiState<T> {            return CommentApiState(Status.ERROR, null, msg)        }         // When the call is loading set the state        // as Loading and rest as null        fun <T> loading(): CommentApiState<T> {            return CommentApiState(Status.LOADING, null, null)        }    }} // An enum to store the// current state of api callenum class Status {    SUCCESS,    ERROR,    LOADING}
 
 
Open utils > AppConfig and add code to create API service, which will be used to make API calls.
 
Kotlin
import com.google.gson.GsonBuilderimport retrofit2.Retrofitimport retrofit2.converter.gson.GsonConverterFactory object AppConfig {     // Base url of the api    private const val BASE_URL = "https://jsonplaceholder.typicode.com/"     // create retrofit service    fun ApiService(): ApiService =        Retrofit.Builder().baseUrl(BASE_URL)            .addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))            .build()            .create(ApiService::class.java)}
 
 
The API part of the App is complete. Now we need to work on ViewModel and repository.
 
Step 6: Working with the Repository
 
Open repository > CommentsRepository. Add the following code. Refer to the comments for an explanation.
 
Kotlin
class CommentsRepository(private val apiService: ApiService) {    suspend fun getComment(id: Int): Flow<CommentApiState<CommentModel>> {        return flow {                         // get the comment Data from the api            val comment=apiService.getComments(id)                         // Emit this data wrapped in            // the helper class [CommentApiState]            emit(CommentApiState.success(comment))        }.flowOn(Dispatchers.IO)    }}
 
 
Step 7: Working with the ViewModel
 
Open ViewModel > CommentViewModel.Add the following code. Refer to the comments for an explanation.
 
Kotlin
import androidx.lifecycle.ViewModelimport androidx.lifecycle.viewModelScopeimport kotlinx.coroutines.flow.MutableStateFlowimport kotlinx.coroutines.flow.catchimport kotlinx.coroutines.flow.collectimport kotlinx.coroutines.launch class CommentsViewModel : ViewModel() {     // Create a Repository and pass the api    // service we created in AppConfig file    private val repository = CommentsRepository(        AppConfig.ApiService()    )     val commentState = MutableStateFlow(        CommentApiState(            Status.LOADING,            CommentModel(), ""        )    )     init {        // Initiate a starting        // search with comment Id 1        getNewComment(1)    }      // Function to get new Comments    fun getNewComment(id: Int) {         // Since Network Calls takes time,Set the        // initial value to loading state        commentState.value = CommentApiState.loading()                 // ApiCalls takes some time, So it has to be        // run and background thread. Using viewModelScope        // to call the api        viewModelScope.launch {                         // Collecting the data emitted            // by the function in repository            repository.getComment(id)                    // If any errors occurs like 404 not found                    // or invalid query, set the state to error                    // State to show some info                    // on screen                .catch {                    commentState.value =                        CommentApiState.error(it.message.toString())                }                // If Api call is succeeded, set the State to Success                // and set the response data to data received from api                .collect {                    commentState.value = CommentApiState.success(it.data)                }        }    }}
 
 
we are almost done, we now need to call the API from view(MainActivity) and show the data on the screen.
 
Step 8: Working with the view (MainActivity.kt) 
 
Open presentation > MainActivity.kt. Add the following code, refer to the comments for explanation.
 
Kotlin
import android.os.Bundleimport android.widget.Toastimport androidx.appcompat.app.AppCompatActivityimport androidx.core.view.isVisibleimport androidx.lifecycle.ViewModelProviderimport androidx.lifecycle.lifecycleScopeimport kotlinx.coroutines.flow.collectimport kotlinx.coroutines.launch class MainActivity : AppCompatActivity() {     // create a CommentsViewModel     // variable to initialize it later    private lateinit var viewModel: CommentsViewModel     // create a view binding variable    private lateinit var binding: ActivityMainBinding     override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)         // instantiate view binding        binding = ActivityMainBinding.inflate(layoutInflater)        setContentView(binding.root)         // initialize viewModel        viewModel = ViewModelProvider(this).get(CommentsViewModel::class.java)          // Listen for the button click event to search        binding.button.setOnClickListener {             // check to prevent api call with no parameters            if (binding.searchEditText.text.isNullOrEmpty()) {                Toast.makeText(this, "Query Can't be empty", Toast.LENGTH_SHORT).show()            } else {                // if Query isn't empty, make the api call                viewModel.getNewComment(binding.searchEditText.text.toString().toInt())            }        }        // Since flow run asynchronously,        // start listening on background thread        lifecycleScope.launch {             viewModel.commentState.collect {                 // When state to check the                // state of received data                when (it.status) {                     // If its loading state then                    // show the progress bar                    Status.LOADING -> {                        binding.progressBar.isVisible = true                    }                    // If api call was a success , Update the Ui with                    // data and make progress bar invisible                    Status.SUCCESS -> {                        binding.progressBar.isVisible = false                                               // Received data can be null, put a check to prevent                        // null pointer exception                        it.data?.let { comment ->                            binding.commentIdTextview.text = comment.id.toString()                            binding.nameTextview.text = comment.name                            binding.emailTextview.text = comment.email                            binding.commentTextview.text = comment.comment                        }                    }                    // In case of error, show some data to user                    else -> {                        binding.progressBar.isVisible = false                        Toast.makeText(this@MainActivity, "${it.message}", Toast.LENGTH_SHORT).show()                    }                }            }        }    }}
 
 
Now run the app, put some numbers and click search. Enter any number from 1-500, It will return a successful state.
 
Output:
 
 
Get the complete project from GitHub.
 
sagartomar9927
Kotlin Android
Picked
Android
Kotlin
Android
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Comments
Old Comments
Flutter - Custom Bottom Navigation Bar
How to Read Data from SQLite Database in Android?
How to Post Data to API using Retrofit in Android?
Android Listview in Java with Example
Retrofit with Kotlin Coroutine in Android
Android UI Layouts
Kotlin Array
Retrofit with Kotlin Coroutine in Android
Kotlin Setters and Getters