3

In my Android app, with Kotlin and using Koin dependency injection, my init block is repeatedly called every time the app is brought into the foreground. The issue this causes is making additional background calls that are not necessary when the app is brought into the foreground, and the View refreshes when I don't always want it too. My question is, how to properly utilize the init block and collect a single flow without the init block methods being repeatedly called? Is init { } always called when a ViewModel is brought into the foreground?

Below is an example of how I'm utilizing the ViewModel in my fragment, and the methods called in my init block:

import org.koin.androidx.viewmodel.ext.android.*

class MyFragment: Fragment() {

     private val viewModel: MyViewModel by viewModel()

     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

         collectSomeFlow()
     }

     private fun collectSomeFlow() {
          lifecycleScope.launchWhenStarted {
            viewModel.onUiStateChanged.collect {
                 // do something here
            }
        }
     }

}

class MyViewModel: ViewModel(private val repository: MyRepository): ViewModel() {

     private val _uiState: MutableStateFlow<MyUiState> = MutableStateFlow(MyUiState.Success(emptyList()))
     val uiState: StateFlow<MyUiState> = _uiState


     init {
         // Repeatedly fired off after View is created if app is resumed again
         getSomeDataFromNetwork()
     }
    
     private fun getSomeDataFromNetwork() {
          viewModelScope.launch {
               val response = repository.getSomeDataFromNetwork()
               // Handle response, update _uiState, etc.
          }
     }

}
General Grievance
  • 4,555
  • 31
  • 31
  • 45

2 Answers2

1

you should use :

  lifecycleScope.launch {
            yourFlow.flowWithLifecycle(this@MyFragment.viewLifecycleOwner.lifecycle, Lifecycle.State.CREATED).collect {
                //collect
            }
        }

or just use an extension function for clean code:

//With Activities:

fun <T> AppCompatActivity.launchAndRepeatWithLifecycleNotNull(
    flow: Flow<T?>,
    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
    block: (T) -> Unit
) {
    lifecycleScope.launch {
        flow.flowWithLifecycle(this@launchAndRepeatWithLifecycleNotNull.lifecycle, minActiveState).collect {
            it?.let(block)
        }
    }
} 

//With Fragments:

fun <T> Fragment.launchAndRepeatWithViewLifecycleNotNull(
    flow: Flow<T?>,
    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
    block: (T) -> Unit
) {
    viewLifecycleOwner.lifecycleScope.launch {
        flow.flowWithLifecycle(this@launchAndRepeatWithViewLifecycleNotNull.viewLifecycleOwner.lifecycle, minActiveState).collect {
            it?.let(block)
        }
    }
}

Usage:

    launchAndRepeatWithLifecycleNotNull(yourFlow, OnWhichStateTheFlowShouldCollect) {
         //collect flow
    }
//OnWhichStateTheFlowShouldCollect on your case is CREATED
Mohmmaed-Amleh
  • 383
  • 2
  • 11
0

Hi seems there is no strange thing running, However, you call request every time when Lifecycle event is in started state You can simply change the collectSomeFlow function

private fun collectSomeFlow() {
      lifecycleScope.launchWhenStarted {
        viewModel.onUiStateChanged.collect {
             // do something here
        }
    }
 }

to

private fun collectSomeFlow() {
      lifecycleScope.launchWhenCreated {
        viewModel.onUiStateChanged.collect {
             // do something here
        }
    }
 }

launchWhenCreated Launches and runs the given block when the Lifecycle controlling this LifecycleCoroutineScope is at least in Lifecycle.State.CREATED state.

https://developer.android.com/reference/kotlin/androidx/lifecycle/LifecycleCoroutineScope

Davud Davudov
  • 45
  • 1
  • 10