how to stop CountDownTimer in another Fragment? - kotlin

I have three fragment, A -> B -> C. I started CountDownTimer in A fragment and want to stop it in C fragment. please help me!

Your best bet would be to use a ViewModel tied to the Activity of the Fragments (so shared between fragments), and start the CountDown there. Then in Fragment C you just need to get the same ViewModel and stop the CountDown from there. |
For example, by using implementation "androidx.fragment:fragment-ktx:1.4.0" you can do in fragment A
private val viewModel: MyActivityViewModel by activityViewModels()
and in onCreateView() call your method that starts the timer viewModel.startCountDown()
Then in Fragment C
private val viewModel: MyActivityViewModel by activityViewModels()
and in onCreateView() call the method stopping it viewModel.stopCountDown()

Related

Coroutine called by event that requires another coroutine to finish

In my ViewModel I have a function that is called from UI. It can be called many times per second.
Data comes from barcode scanner ViewModel. I'm passing it from one ViewModel to another thru UI for simplicity and to reuse barcode scanner ViewModel.
For simplicity lets assume that I have something like this:
// called from the fragment (that observes some property on the another viewmodel)
public fun processScannedBarcode(barcode : String){
// process data after necessaryData is ready (not null?)
val item = findItemByBarcode(barcode)
}
private var dataFromApi: ArrayList<Item>?
private fun loadNecessaryDataFromTheApi(){
viewModelScope.launch {
canReload = false
dataFromApi = myapi.getDataFromApi() // suspend fun inside
canReload = true
}
}
// called from the fragment button click
public fun reloadNecessaryDataFromTheApi(){
loadNecessaryDataFromTheApi()
}
init {
loadNecessaryDataFromTheApi()
}
My data required to process may be not ready yet because it comes from the API. I have to deferr that processing and resume when data is ready.
I could simply solve this with some Queue and a Thread, but maybe it is possible to do that with Kotlin coroutines?
As your loadNecessaryDataFromTheApi() only sets properties, it is not that easy to observe it or wait for it. You need to use some synchronization utility like locks, channels, etc. In this case it will be probably the easiest to keep a Deferred of the necessary data:
public suspend fun processScannedBarcode(barcode : String){
val data = dataFromApi.await()
val item = findItemByBarcode(barcode)
}
private val dataFromApi = CompletableDeferred<ArrayList<Item>>()
private fun loadNecessaryDataFromTheApi(){
viewModelScope.launch {
canReload = false
dataFromApi.complete(myapi.getDataFromApi())
canReload = true
}
}
Some notes:
We have to make processScannedBarcode suspend if it is going to wait for something.
If you want reloadNecessaryDataFromTheApi() to defer processing barcodes again, simply replace dataFromApi with a new deferred.

Access fragment view in parent activity

I have an activity which displays multiple fragments depending on which one is selected.
I also have a button in this activity and I want to obtain a value from a specific fragment when this button is clicked.
How can I obtain this value?
I tried to get the view I wanted from the fragment from the activity as the code shows below but I can understand that it doesn't work since the fragment is still to be created.
onOffButton.setOnClickListener {
if (onOffButton.text.contains("ON")) {
onOffButton.text = "TURN OFF"
var hoursPicker = findViewById<NumberPicker>(R.id.hoursPicker)
}
}
The short version is you shouldn't do this, there are all kinds of complications (especially when you're trying to access the Fragment's Views).
It's even more complicated if the Fragment might not even be added to the UI at all! If it's not there, what value are you supposed to use? If you want to somehow create the Fragment just so it exists, and so you can read the value from its text box, then that's a sign the value really needs to be stored somewhere else, so you don't need the Fragment if you want to access it.
The easiest, recommended, and modern way to share data like this is with a ViewModel:
class MyViewModel : ViewModel() {
// setting a default value here!
var currentHour: Int = 0
}
class MyActivity : AppCompatActivity() {
val model: MyViewModel by viewModels()
fun onCreate(...) {
...
onOffButton.setOnClickListener {
// access the data in the ViewModel
val currentHour = model.currentHour
}
}
}
class MyFragment : Fragment() {
// using activityViewModels so we get the parent Activity's copy of the VM,
// so we're all sharing the same object and seeing the same data
val model: MyViewModel by activityViewModels()
fun onViewCreated(...) {
...
hoursPicker.setOnValueChangeListener { _, _, newValue ->
// update the VM
model.currentHour = newValue
}
}
}
So basically, you have this ViewModel object owned by the Activity and visible to its Fragments. The VM outlives all of those components, so you don't lose data while an Activity is being destroyed on rotation, or when a Fragment isn't added to the UI, etc.
The VM is the source of data, everything else just reads from it, or updates it when something changes (like when the Fragment updates the variable when its number picker's value changes). This way, the Activity doesn't need to go "ask" the Fragment for info - it's stored in a central location, in the VM
This is the most basic way to use a ViewModel - you can start using LiveData and Flow objects to make different UI components observe data and react to changes too. For example, your button in your Activity could change some enabled state in the VM, and the Fragment (if it's added) will see that change and can do things like make the number picker visible or invisible.
It's way easier to coordinate this stuff with a ViewModel, so if you don't already know how to use them, I'd recommend learning it!

Correct use CoroutineScope with retrofit api call

Is this correct Coroutine and retrofit use?
i have some problems with interception of service (RuntimeException in geting getAccessTokenBlocking via intercept), and maybe it due to incorrect CoroutineScope use?
override fun onBindViewHolder(...){
val service = serviceBuilderFactory
.create(serviceEndpoint, acc)
.buildWithCache(Service::class.java)
CoroutineScope(Dispatchers.IO)
.launch(exceptionHandler) {
val obj = service.getObj()
//should i use here for webView.post - withContext(Dispatchers.Main) { ??????
// or shoud i use async and resume webView.post after callback?
webView.post {
webView.loadUrl(obj.url)
}
}
}
retrofit
#GET("/url")
suspend fun getObj(): Obj
This shouldn't be in an adapter at all. You're firing off coroutines every time an item scrolls onto the screen, and since you create a one-off CoroutineScope to launch each one, you have no means of cancelling them. If the user rotates the screen a couple of times quickly, you'll have like 30 obsolete coroutines running in the background that you can't cancel, and many of them will be doing redundant work.
Instead, you should do the fetching in a ViewModel so the fetches don't have to be repeated redundantly when the screen is rotated. And use viewModelScope to launch the coroutines so they'll be automatically cancelled when they become obsolete from the current screen going out of scope.
If you don't mind pre-fetching each item's data before showing it in the RecyclerView, you can map your data type to include the fetched URL before you even expose it to your Activity/Fragment via a LiveData or Flow.
If you want to lazily start loading the URL only when the item appears on screen, you can map your data type to a Deferred<String> using async(start = CoroutineStart.LAZY) { ... }. Then add a CoroutineScope parameter to your adapter's constructor so the Activity can pass lifecycleScope or Fragment can pass viewLifecycleScope, and you can use that scope to launch coroutines in the adapter that will automatically be cancelled when obsolete. And you can use these coroutines to await() the deferred URL.

Why do the author need to use asStateFlow() in Compose?

The Code A is a sample code from the video.
I can't understand why the author need to use asStateFlow().
I think the Code B is OK, right?
Code A
class MainViewModel: ViewModel() {
private val _stateFlow= MutableStateFlow("Hello World")
val stateFlow = _stateFlow.asStateFlow()
...
}
Code B
class MainViewModel: ViewModel() {
private val _stateFlow= MutableStateFlow("Hello World")
val stateFlow = _stateFlow
...
}
Code A makes the stateFlow read-only while Code B exposes the mutable state flow as is.
The documentation of asStateFlow() is pretty clear about this:
Represents this mutable state flow as a read-only state flow.
If you take a look at the implemenation, you can see that it wraps the current (mutable) flow in a ReadonlyStateFlow which is, well, read-only:
public fun <T> MutableStateFlow<T>.asStateFlow(): StateFlow<T> =
ReadonlyStateFlow(this, null)
To make it easier to understand, if you use MainViewModel in a component, this outsider will be able to read the values but not write them in case of Code A. Meanwhile, if you use Code B, the outsider component may emit its own values to the state flow. This is usually undesirable as it should be the ViewModel's responsibility to emit data (this is called unidirectional data flow) as a response to actions coming from the observers (the view components).

Trying to use ViewModels inside of another ViewModel, Errors with LifecycleObserver and Ownership (Kotlin)

Im trying to get some data out of other ViewModels inside another ViewModel to make my code smaller, but im having a problem trying to implement what already worked on a fragment or in a activity, this is what i got:
class ObraConMediaViewModel(private val context: ViewModelStoreOwner,
private val id: Int): ViewModel(), LifecycleObserver {
var allObras: LiveData<ArrayList<ObraConMedia>>
private lateinit var viewModelobras: ViewModelObras
private lateinit var viewModelMediaObra: ViewModelMediaObra
val repositoryobras =ObrasRepository()
val repositoryMediaObra = MediaObraRepository()
val viewModelFactoryobras = ViewModelFactoryObras(repositoryobras)
val viewModelMediaObraFactory = ViewModelMedIaObraFactory(repositoryMediaObra)
init{
viewModelobras = ViewModelProvider(context, viewModelFactoryobras)
.get(ViewModelObras::class.java) // requireActivity() when called
viewModelMediaObra = ViewModelProvider(context, viewModelMediaObraFactory)
.get(ViewModelMediaObra::class.java)
viewModelobras.getObras(id)
viewModelobras.myResponse.observe(this , Observer { response ->
if (response.isSuccessful){
Log.d("Response", response.body()?.ans?.get(0)?.autor)
Log.d("Response", response.body()?.ans?.get(1)?.autor)
}else{
Log.d("Response", response.errorBody().toString())
}})
viewModelMediaObra.getMediaObra(Constantes.PRUEBA_ID)
viewModelMediaObra.myResponse.observe(this, Observer { response ->
if (response.isSuccessful){
Log.d("Response", response.body()?.ans?.get(0)?.filePath)
}
})
}}
I was having trouble with the Observer but extending the class to LifecycleObserver fixed it, i have no idea if this will even work but the only error that i have right now its the owner of the .observe(this,....), i dont seem to find a way to pass a lifecycleowner from the fragment to this viewmodel. All the variables i need to make this viewmodel work are inside those two responses. If this is a very bad way to do it please tell me. Thanks for reading.
Kindly note that above approach is not correct.
One should not create a instance of ViewModel inside another ViewModel.
There is a possibility that one ViewModel may get destroyed before another. This will lead to garbage reference and memory leaks.
I would recommend you to create the instance of both View Models in an Activity/Fragment and then call respective methods of ViewModel from Activity/Fragment.
Also, as you want to make your code smaller and concise, I highly recommend you Shared ViewModel.
This Shared ViewModel can be used by two fragments.
Please refer to this link