How to use Kotlin Flow in android development (2)

In android development, we can use flow to collect data for Fragment and Activity, and then display it. When Fragment and Activity are not visible, collection should be stopped. In the practice of ViewModel, LiveData is a typical representative. These components are able to sense changes in the life cycle of Fragment and Activity. Flow can do that too.

Demo download link

First of all, the app's dependency cannot be without the following configuration:

// Coroutines(includes kotlin flow)
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0"
// test Coroutines flow
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4"
// 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"

It is quite convenient to use Flow to implement MVVM. Let's describe it roughly:

Please add a picture description

This is our data flow.

Create the project's directory structure

First, highlight our directory first. This directory structure corresponds to each link of our data flow above.
Please add a picture description

Next, let's talk about their implementation layer by layer.

model layer

The format of the data we will get from the network is as follows:

{
    
    
  "postId": 1,
  "id": 5,
  "name": "vero eaque aliquid doloribus et culpa",
  "email": "[email protected]",
  "body": "harum non quasi et ratione\ntempore iure ex voluptates in ratione\nharum architecto fugit inventore cupiditate\nvoluptates magni quo et"
}

Therefore, we define such a data model:

@Keep
data class CommentModel(
    val body: String,
    val email: String,
    val id: Int,
    val name: String,
    val postId: Int
)

Data Services at the Network Layer

Because we are using Retrofit, the service interface is defined in this way first, and our data model is returned through this service.

interface GetCommentService {
    
    
    @GET("comments/{id}")
    suspend fun getCommentWithId(@Path("id") id: Int): CommentModel
}

database

Next, define the data warehouse, which is mainly used to obtain multiple data sources. Currently we only have one, so this can be determined as follows:

class CommentRepository(private val apiService: GetCommentService) {
    
    

    suspend fun getCommentWithId(id: Int): Flow<CommentModel> {
    
    
        return flow {
    
    
            val data = apiService.getCommentWithId(id)
            emit(data)
        }.flowOn(Dispatchers.IO)
    }

}

Here is one of the key points of our application of Flow. First, the flow operator creates a Flow, which is a cold flow, that is, as long as the terminal operator collect is used, it can be triggered to call the interface of the network layer. Because Flow is a technology built on top of coroutine, and our network layer service is also suspend, that is to say, it can be suspended. Then, that is to say, a pair of this flow object calls the collect operator to make a network request, and the network request Before the end, it will be suspended, and the operation of emit(data) will continue after the network returns. flowOn just switches the execution of flow up and down to IO.

ViewModel

This layer is responsible for calling the data source of the data warehouse and exposing the data to the presentation layer.


class CommentViewModel : ViewModel() {
    
    

    private val BASE_URL = "https://jsonplaceholder.typicode.com/"

    private val apiService = Retrofit.Builder().baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
        .build()
        .create(GetCommentService::class.java)

    private val repository = CommentRepository(apiService)

    private val commentMutableStateFlow = MutableStateFlow<CommentModel?>(null)
    val commentStateFlow: StateFlow<CommentModel?> = commentMutableStateFlow

    init {
    
    
        getACommentWithId(1)
    }
    
    fun getACommentWithId(id: Int) {
    
    
       val job = viewModelScope.launch {
    
    
           repository.getCommentWithId(id).catch {
    
    
               commentMutableStateFlow.value = null
           }.collect {
    
    
               commentMutableStateFlow.value = it
           }
       }
    }
}

The code responsible for calling the source of the data warehouse in this layer must also be called in the context of the coroutine:

val job = viewModelScope.launch {
    
    
           repository.getCommentWithId(id).catch {
    
    
               commentMutableStateFlow.value = null
           }.collect {
    
    
               commentMutableStateFlow.value = it
           }
       }

ViewModel provides such a coroutine context viewModelScope, which actually provides a coroutine execution scope, and there is a corresponding Context in this scope.

After talking about calling, then ViewModel also uses Flow to expose data to the presentation layer, and here is a trick for everyone:

    private val commentMutableStateFlow = MutableStateFlow<CommentModel?>(null)
    val commentStateFlow: StateFlow<CommentModel?> = commentMutableStateFlow

We use this commentMutableStateFlow to update data, it is private, so the presentation layer cannot change its value, and we don't want it to change, so we will provide an immutable, readable variable to the presentation layer: commentStateFlow.

presentation layer

 private lateinit var commentViewModel: CommentViewModel
    private var currentId = 1;

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

        val button = findViewById<Button>(R.id.button)
        val textView = findViewById<TextView>(R.id.textView)
        button.setOnClickListener {
    
    
            currentId += 1
            commentViewModel.getACommentWithId(currentId)
        }
        commentViewModel = ViewModelProvider(this).get(CommentViewModel::class.java)

        lifecycleScope.launchWhenStarted {
    
    
            commentViewModel.commentStateFlow.collect {
    
    
                it?.let {
    
     comment ->
                    textView.text = "${
      
      comment.id} \n\n ${
      
      comment.email} \n\n ${
      
      comment.body}"
                }
            }
        }
    }

The place where the presentation layer reads data must also be in a coroutine scope.

That's it for the simple use of Flow.

Guess you like

Origin blog.csdn.net/weixin_40763897/article/details/128798218