In this code:
class RequestNewPasswordFragment {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnRequestNewPassword.setOnClickListener {
view.hideKeyboard()
viewModel.validateEmail(txtInputLayoutEmail.textValue)
}
disposables += viewModel.emailValidationSubject
.observeOnMainThread()
.subscribe { validationResponse ->
viewModel.requestNewPassword()
}
}
When the fragment is initialized, the emailValidationSubject gets initialized. This causes the code in subscribe to execute, which makes a call to the requestNewPassword in the viewModel. I want to avoid this. I want this to only be called when btnRequestNewPassword is clicked. The code in subscribe should only get called when the viewModel needs to validate the input. How can I prevent viewModel.requestNewPassword() from being called when the fragment is initialized?
I'm assuming your emailValidationSubject is a BehaviourSubject based on your previous question here.
BehaviourSubject will always emit an value on subscription, hence you need to provide an initial value.
it begins by emitting the item most recently emitted by the source Observable (or a seed/default value if none has yet been emitted)
You need to use a PublishSubject:
PublishSubject emits to an observer only those items that are emitted by the source Observable(s) subsequent to the time of the subscription.
Related
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.
I'm using MVVM as architecture on my app, but I watch an unexpected escenario in the observer code: Inside the observer always entered one time before get the real value.
myViewModel.getUserInfo().observe(this, androidx.lifecycle.Observer { user ->
if (user!= null) {
} else {
//THE FIRST TIME THROW HERE
}
In my viewModel I have this:
class MyViewModel : ViewModel() {
fun getUserInfo(): MutableLiveData<UserInfoResponse> {
val liveData: MutableLiveData<UserInfoResponse> = MutableLiveData()
liveData.postValue(UserInfoResponse("user"))
return liveData
}
Can anyone got the idea that is happening?
Thanks
postValue()
Posts a task to a main thread to set the given value. So if you have a following code executed in the main thread:
liveData.postValue("a");
liveData.setValue("b");
The value "b" would be set at first and later the main thread would override it with the value "a".
If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.
At a guess, you're posting that value before you return the LiveData, but it actually gets set after the observer has been added and received the first value. When that happens, the observer is called again with the second, posted value.
You've mentioned adding and removing fragments, and I'm assuming they're using the same ViewModel, so when they start observing the LiveData it already has a value set from earlier, and the observer receives that immediately. The posted value comes later.
I'm trying to store value using DataStore.
class BasicDataStore(context: Context) :
PrefsDataStore(
context,
PREF_FILE_BASIC
),
BasicImpl {
override val serviceRunning: Flow<Boolean>
get() = dataStore.data.map { preferences ->
preferences[SERVICE_RUNNING_KEY] ?: false
}
override suspend fun setServiceRunningToStore(serviceRunning: Boolean) {
dataStore.edit { preferences ->
preferences[SERVICE_RUNNING_KEY] = serviceRunning
}
}
companion object {
private const val PREF_FILE_BASIC = "basic_preference"
private val SERVICE_RUNNING_KEY = booleanPreferencesKey("service_running")
}
}
#Singleton
interface BasicImpl {
val serviceRunning: Flow<Boolean>
suspend fun setServiceRunningToStore(serviceRunning: Boolean)
}
And in a Service, trying to monitor that value, here is the respective code :
private fun monitorNotificationService() {
Log.d("d--mua-entry-service","entry")
CoroutineScope(Dispatchers.IO).launch {
Log.d("d--mua-entry-service","entry scope")
basicDataStore.serviceRunning.collect{
Log.d("d--mua-entry-service","$it current status - collect")
}
basicDataStore.serviceRunning.onEach {
Log.d("d--mua-entry-service","$it current status - on each")
}
}
}
In EntryService :
init {
basicDataStore = BasicDataStore(this)
}
But it seems that onEach isn't working at all. And collect is working once, as it should. So how should I monitor/observe a flow?
Logcat :
2021-03-25 20:41:49.462 30761-30761/com.mua.roti D/d--mua-entry-service: entry
2021-03-25 20:41:49.465 30761-30900/com.mua.roti D/d--mua-entry-service: entry scope
2021-03-25 20:41:49.471 30761-30901/com.mua.roti D/d--mua-entry-service: false current status - collect
I haven't used DataStore, but I expect the Flow is infinite, meaning it never finishes collecting until you cancel the coroutine. So any code below the first call to collect() will never be reached. Also, when you call onEach, it simply returns another Flow. The code in onEach won't be called until you collect that returned Flow. onEach is for tacking on side effects to the collecting you will be doing later. Or alternatively for setting up what to do when using launchIn to collect it.
It is a code smell to be creating a CoroutineScope that you don't store in a property for cancellation. The point of the scope is that you can cancel it when the lifetime of the associated component is over. If you create it and throw away your reference like this, you can never cancel its children, so calls to collect, for example, will leak the surrounding class. If you are working on Android, you rarely if ever need to create any CoroutineScope because the framework provides scopes for various lifecycle components like Activity, Fragment, ViewModel, and LifecycleService.
In a fragment or an activity, is there a recommended order to setting the observer vs initiating the data producer?
For example, assuming no others are fetching data or observing, then:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// OPTION A: this seems bullet-proof. Setup the observer first,
// then trigger the generation of the data...
// ----------------------------------------------------------
// 1) setup observer
mainViewModel.myResponse.observe(viewLifecycleOwner, { response -> {...} })
// 2) initiate data fetching
mainViewModel.generateFetchInBackground()
// OPTION B: this is what I sometimes see done. This seems like
// a race condition since the triggering of generation happens
// first, then the observer is established...
// ----------------------------------------------------------
// 1) initiate data fetching
mainViewModel.generateFetchInBackground()
// 2) setup observer
mainViewModel.myResponse.observe(viewLifecycleOwner, { response -> {...} })
}
It doesn't mater when you start providing data or observing it. LiveData guaranties all the values will be delivered to your observers when they become active.
When you update the value stored in the LiveData object, it triggers all registered observers as long as the attached LifecycleOwner is in the active state.
LiveData allows UI controller observers to subscribe to updates. When the data held by the LiveData object changes, the UI automatically updates in response.
That's why LiveData help you decouple producer and observers. And there is no race condition on LiveData because it delivers all of the data on the main thread.
I have a code where I am lazily updating the UI using kotlin couroutines.
When I am putting some code inside GlobalScope.async only the first few lines are executed and the rest of the code doesn't
class MyFragment : Fragment(), CoroutineScope {
private lateinit var masterJob: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + masterJob
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
GlobalScope.async(coroutineContext) {
api.fetchOrders()
configureOrders(view!!)
// Some code here ...
}
}
For example in the above code the code after the comment doesn't get executed. And if I swap the code above the comment with the one above it then it doesn't get executed.
What am i missing ?
When you have an async method that returns something, is async and the problem is that only the first lines of the method are executed, is because you are not waiting the method to finish with the key word await.
Something needs the value returned from the function and forces the function to end, unless you use await, then it must wait the function to end.
The code is not right because your coroutines context is running on the main thread with that Dispatchers.Main keyword on the coroutine context initialization . If you want to perform API calls please change to Dispatchers.IO .
I assume api.fetchOrders() can't be run on the Main thread . Plus you need to await that response with the keyword .await() :
val yourData = api.fetchOrders().await() which will return what is inside your Deferred type . For example if it is an Deferred<ArrayList> it returns the ArrayList after you call the await() method .
Note .
If you call that await() on that coroutine context I believe you should have an error like NetworkOnMainThreadException
I found out what the issue was, the code was working but it was causing an exception which was not visible in the stack trace (not sure why).
When I changed GlobalScope.async to GlobalScope.launch it started working
A couple of things:
Your fragment is a scope but you still use GlobalScope. Dispatchers.Main + masterJob is completely ignored
If you'd use your fragmentScope, depending on the implementation of api.fetchOrders, your app could crash because you are still on the main thread
It appears you are not interested in the result, so don't use a Deferred
If async is what you want, you have to call await