UI Change in Back Ground Thread - android

i have a function and this function send http request in back Ground Thread
when request is excute and complete i have some line of code to make UI Change but when fragment pause the app crash Because the function cant make UI Change when fragment pause i dont now what i must do for solve this
val url = "BASE_DIR/example"
val client = OkHttpClient()
val request = Request.Builder().url(url).build()
val code = client.newCall(request).execute().code()
if (code == 200) {
fragment.requireActivity().runOnUiThread {
fragment.refresh.isRefreshing = false
}

Another way to send a message to the UI thread is with a Handler
Handler(Looper.getMainLooper()).post {
// do whatever
}
or you have the usual postDelayed methods etc. So you can update fragment without it needing to be attached to an Activity, which is probably better than just doing a null check on getActivity (the fragment might just be temporarily detached, so you still want to update its state so you don't "lose" the result).
If you don't mind losing the result, because you're treating the detached fragment as dead so it doesn't need to be updated, then the null check like in #rahat's answer is the simplest way to do it. Don't make it more complicated than it needs to be!

Try with
fragment.getActivity()?.runOnUiThread {
fragment.refresh?.isRefreshing = false
}
The issue is when the fragment is detached the requireActivity()
will throw IllegalStateException so since it is not associated with any activity, but getActivity() returns null if it is not associated with any activity, so with null safe call you can do it safely.

Related

Why LiveData can receive the last available data automatically?

I am reading LiveData source code, and for this method:
public void observe(#NonNull LifecycleOwner owner, #NonNull Observer<?super T> observer) { .. }
And this is part of its doc:
When data changes while the owner is not active, it will not receive
any updates. If it becomes active again, it will receive the last
available data automatically.
I was trying to figure out why LiveData can achieve this lifecycle-awareness. I read source code of setValue but still couldn't get it. Can anybody help me with the on the general idea?
Suppose you are not using live data and you are showing list of data with pagination concept . You as user have scroll down to view more and more data and application is calling apis to get data as you scroll . Now you have rotated your device so as developer we know that your activity will be recreated and user will be at initial stage again as all things were destroyed .. Well you can achieve this using onSaveInstance but you will have to code and manage yourself ..
So user will have to scroll again to view all data but imagine if you have some mechanism where you get your last updated data whenever activity lifecycle changes so you can easily set data again and allow user to use your app like nothing happened .. and here livedata concept come into picture with lifecycle awareness
Hope this answer will clear your doubts
Edit :-
To understand how they are managing lifecycle , you can visit this link
https://developer.android.com/topic/libraries/architecture/lifecycle#lc
here is source code method of LiveData class where you can see(at last line) how they are adding lifecycler owne to observer
Read the source code of LiveData.java (in lifecycle-livedata-core:2.2.0#aar) again, it seems clear to me now.
When adding an observer to LiveData via liveData.observe(lifecycleOwner, Observer { .. } ), the magic happens in the observe method.
In the observe method, it puts the lifecycleOwner and the Observer into a new object called LifecycleBoundObserver like this:
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
// ... some other code
owner.getLifecycle().addObserver(wrapper);
Important to note and we can also tell it from the last statement: the LifecycleBoundObserver is an instance of LifecycleObserver, that means, it can be notified when the given lifecycleOwner gets state updated, the key lies in the onStateChanged method of LifecycleBoundObserver.
#Override
public void onStateChanged(#NonNull LifecycleOwner source,
#NonNull Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
So:
If the lifecycleOwner (the Activity or Fragment) gets DESTROYED, it will remove the observer (the callback observer, not the LifecycleObserver), hence the observer won't be notified for new data once it's DESTROYED.
If it is not DESTROYED, it requires the lifecycleOwner in Active states (STARTED or RESUMED), this is restricted by the return value from the method shouldBeAlive(), and finally the new data gets delivered to observer callback in the method activeStateChanged.
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
boolean wasInactive = LiveData.this.mActiveCount == 0;
LiveData.this.mActiveCount += mActive ? 1 : -1;
if (wasInactive && mActive) {
onActive();
}
if (LiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
if (mActive) {
dispatchingValue(this);
}
}
If the input param newActive is true, then finally it will reach the statement of dispatchingValue(this) - the last statement, and if it is false (i.e.: inactive states: PAUSED / STOPPED / DESTROYED), it won't call dispatchingValue(this), thus the observer callback won't be triggered.
If the Activity / Fragment goes back to foreground from background, it becomes Active again, then the LifecycleBoundObserver will be notified and the onStateChanged will be called again, and this time, when calling activeStateChanged(newActive), it passes true, therefore, dispatchingValue(this) will be called, and the latest data set via setValue or post will be picked up, that's the reason for explaining why the Activity can get the last emitted / latest value of LiveData.

Unable to trigger an Android Activity from RxJava2 chain [Android][RxJava]

I have been trying to write an RxJava2 chain and trigger an Android Activity from this chain. The idea is to get an Observable on which I can publish the result of the activity using an interface callback. Here is the code that I call from my Activity to launch the sso flow.
final Disposable disposable = userAuthManager
.initateSignup(this)
.map(ssoResponse -> {
<Do some stuff off the main thread>
return ssoResponse;
})
.subscribe(....)
UserAuthManager.initateSignup(Activity activity) goes like this:
override fun initateSignup(starterActivity: Activity): Single<SSOResponse> {
return ssoService.startSSO(<someData>, starterActivity)
.map {
....... Some business logic and then returning the response
// After handling, finally returning the SSOResponse ...
it
}
}
Finally SSOService.startSSO(<someData>, starterActivity) goes like this:
override fun startSSO(oldUserSessionJID: String?, starterActivity: Activity): Single<SSOResponse> {
val intent = Intent(starterActivity, <MyTargetActivity>::class.java)
starterActivity.startActivity(intent)
// The below statement prints `true`
Timber.d("This is main thread: " + (Looper.myLooper() == Looper.getMainLooper()))
listenableFuture = SettableFuture.create()
return Single.fromFuture(listenableFuture)
}
In the code above for startSSO, I return a Single from a SettableFuture which I set once I get the response inside the activity.
Issue: MyTargetActivity launches with a black screen and nothing renders on it. Also, the debug points inside the activity behave weirdly. Sometimes they work and sometimes they don't. I have tried launching activity by directly calling SSOService.startSSO(<someData>, starterActivity) from a button click and it works fine. Also, I was thinking that the issue might be related to activity being launched on a non-UI thread and hence I logged that as well. The activity seems to be launched on a UI thread based on the log statement. What is the problem here?
PS: I have removed observeOn() and subscribeOn() calls to make things simple and assume everything runs on the UI thread.

Android ViewModel observer not called from background

I'm working on small android app using MVVM pattern.
My issue is that my ViewModel observer in MyActivity not called from the background. I need it to be called even if the app is in background to show system Notification to the user that app calculation process is done and the result is ready.
This is the current implementation located in onCreate in MyActivity:
mainActivityViewModel.getTestResult().observe(MainActivity.this, new Observer<String>() {
#Override
public void onChanged(#Nullable String blogList) {
Toast.makeText(getApplicationContext(), "test...", Toast.LENGTH_SHORT).show();
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)){
//The app is in foreground - showDialog
}else{
//The app is in background - showNotification
}
}
For now, this observer will be called only if the app is in foreground - if the process done while app was in foreground - 'showDialog' will trigger, if the app was in background - the showNotification will trigger - but only after I will open the app again. It's not the behaviour that I try to achieve. Please help! Thanks.
onChanged will only be called if the Activity's current Lifecycle state is at least STARTED. onPause gets called when you leave the Activity, which means it's not at least STARTED.
LiveData is simply not suitable for the behavior you're trying to achieve.
I would recommend you to use a foreground Service instead. Especially if the mentioned "calculation process" is something that the user should be aware of.
edit:
Let's say you're performing some potentially long running task in the background and you want to continue this task even if the user would leave or even close your Activity. Then using a Service is a good option, and especially a foreground Service if the task is the result of a user action. For example, the user clicks an "upload" button, a foreground Service performs the task and the associated Notification says "Upload in progress".
You have the option to either
Always show a new Notification when the task is complete, regardless of if the Activity is shown or not. This is pretty common.
Only show the Notification if the Activity is not currently started, and if it is started, show something in the Activity view instead.
In order to do the latter option, you need to know the current status of the Activity's Lifecycle. You want to be able to do the following check from your service somehow: getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)
The best way to communicate between an Activity and Service is binding to the Service and extending the Binder class in the Service.
After binding, you may store the Activity Lifecycle status in a variable in the Service, or even provide the Activity itself to the Service.
I guess your getTestResult() in ViewModel returning some live data.
So first of all, you are assigning your real data with LiveData using .setValue(some_data) method. And it is working fine while app is open. Btu when your app is in background. You need to use .postValue(some_data) method to assign data with that LiveData.
Check difference below:
setValue()
Sets the value. If there are active observers, the value will be dispatched to them. This method must be called from the main thread.
postValue()
Posts a task to a main thread to set the given value. If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.
Conclusion, the key difference would be:
setValue() method must be called from the main thread. But if you need set a value from a background thread, postValue() should be used.
I saw this question researching for the same issue and even though it was asked 2 years ago I was able to let LiveData notify the observer even though the Fragment (or in question's case, an Activity) is either paused or stopped, so I am posting my solution here.
The solution is for a fragment, but can be adapted to activities as well.
On the fragment:
class MyFragment: Fragment() {
private var _lifecycleWrapper: LifecycleOwnerWrapper? = null
val activeLifecycleOwner: LifecycleOwner
get() {
if (_lifecycleWrapper == null)
_lifecycleWrapper = LifecycleOwnerWrapper(viewLifecycleOwner)
return _lifecycleWrapper!!
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
// On the livedata, use "activeLifecycleOwner"
// instead of "viewLifecycleOwner"
myLiveData.observe(activeLifecycleOwner) { value ->
// do processing even when in background
}
}
override fun onDestroyView() {
super.onDestroyView()
_lifecycleWrapper = null
}
}
LifecycleOwnerWrapper:
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
/**
* A special lifecycle owner that lets the livedata
* post values even though the source lifecycle owner is in paused or stopped
* state. It gets destroyed when the source lifecycle owner gets destroyed.
*/
class LifecycleOwnerWrapper(sourceOwner: LifecycleOwner):
LifecycleOwner, LifecycleEventObserver
{
private val lifecycle = LifecycleRegistry(this)
init
{
sourceOwner.lifecycle.addObserver(this)
when (sourceOwner.lifecycle.currentState)
{
Lifecycle.State.DESTROYED -> lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
Lifecycle.State.CREATED -> lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
Lifecycle.State.STARTED -> lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
Lifecycle.State.RESUMED -> lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
else ->
{
// do nothing, the observer will catch up
}
}
}
override fun getLifecycle(): Lifecycle
{
return lifecycle
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event)
{
if (event != Lifecycle.Event.ON_PAUSE && event != Lifecycle.Event.ON_STOP)
lifecycle.handleLifecycleEvent(event)
}
}
The only thing you need to do is to not call this after onDestroy (or for viewLifecycleOwner, after onDestroyView) otherwise the lifecycle owner will be stale.
What you are trying to do is possible but not in the way you are doing it.
The whole purpose of the LiveData API is to link the data layer with the UI in a life cycle aware manner, so when the app is not in foreground then the observer knows that and stop updating the UI.
The first argument on the observer is the lifecycle.
This is a great improvement because without it the crashes because UI was not available were too often or it was too complex to control manually (boilerplate, edge cases, etc).
Service is not a good idea because the services can be killed by the DALVIK or ANT machine if the memory is needed for the foreground app. Services are not in the foreground but that doesn't mean that are bound to background neither that are guaranteed to be working for a undeterminated span of time.
For doing what you wish use the WorkManager. The WorkManager allows you to schedule jobs with or without conditions and from there you are gonna be able to send a Notification to the user.
You can try for a combination of Workmanager and Viewmodel to achieve an foreground/background app functionality.
For this use the Activity life cycle:
Use the onResume method to remove any WorkManager and star using the ViewModel
Use the onPause method to star the WorkManager
To handle the declaration, you can edit or dismiss the declaration from inside the function in your ViewModel class where the data was successfully retrieved.
private fun dataShow(list: List<String>) {
//Notification cancel
NotificationManagerCompat.from(getApplication()).cancel(30)
if (list.isNotEmpty()) {
data.value = list
progressHome.value = false
} else {
progressHome.value = true
}
}

Kotlin Android Extensions : java.lang.IllegalStateException: view must not be null

I am using kotlin view binding in my fragment. In some cases app crashes with IllegalStateException & view as null, I am accessing it in a runnable which is called using a handler with a 1.5sec delay.
numberRunnable = Runnable {
if (mobileView.text.toString().isNotEmpty() && Utility.isMobileNumberValid(mobileView.text.toString())) {
presenter.getCustomerDetails(Utility.getServerAcceptableContactNumber(mobileView.text.toString()))
}
}
mobileView is null
Handler code:
handler.postDelayed(numberRunnable, 1500)
I am aware of one possibility to check if isAdded in my fragment, but since I cannot replicate the bug I am not sure if its the problem.
The action is likely running after the user leaves the Fragment and onDestroy() is called. In that state, there will be no View instances in the Fragment.
A simple workaround would be to create a global var to check your Fragment's created state. Set it to true in onViewCreated() and false in onDestroyView() (before the super call). Then check that value inside the Runnable before executing your logic.
A better solution (although this is subject to race conditions and needs every Runnable being assigned to a global variable) might be to use the Handler.removeCallbacks() method and pass all your Runnables.
override fun onDestroyView() {
handler.removeCallbacks(numberRunnable)
}
Yet another possibility is to simply say the View is nullable:
mobileView?.let {
//Your stuff goes in here
//You can reference mobileView using "it"
}
You cannot assume that after 1.5s the views are still attached to the view hierarchy.
Add handler.removeCallbacks(numberRunnable) to your onStop() life-cycle callback to remove the numberRunnable when the fragment is not active anymore.
Also ask yourself the question of why you need to have the delay.

If my RxJava 2 call returns a Single or Maybe, do I still need to use CompositeDisposable?

Suppose I have
Disposable disposable = signOutUser()
.subscribe((Response<ResponseBody> response) -> {
if (response.isSuccessful()) {
Intent intent = new Intent(view.getContext(), SignInUserActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NO_HISTORY);
view.getContext().startActivity(intent);
((FragmentActivity) view.getContext()).finish();
}
}, (Throwable ex) -> {
Log.e(TAG, "signOutUser: " + ex.getMessage());
});
where signOutUser() returns Single<Response<ResponseBody>>. When signOutUser() is successful, there is an Intent and the current activity is finished(). Otherwise, it fails, possibly due to network error, so there is no intent and the user stays on current activity.
Since this isn't something to observe (it's a one time event, success or fail), and IF the user successfully logs out, then onDestroy will be called which calls compositeDisposable.clear() which clears all the disposables. So then I'm adding a Disposable and immediately that Disposable is being disposed or cleared.
My question is, do I event need to use Composite Disposable? Do I immediately call disposable.dispose() after subscribe? Do I set anything to null? Do I not use Single?
Do I event need to use Composite Disposable?
Yes, you should always use composite disposable (or normal Disposable), and unsubscribe from it when the time comes (onDestroy/onStop whathere you need). The reason for it is that the network call may be finished after you have closed activity, which will result in memory leaks or even crashes (because context will be null).
Do I immediately call disposable.dispose() after subscribe?
No, because this would result in the call to never return a result. If you dispose immediately after calling subscribe, you will never get a response from it. Read about what happens after you dispose an observable.
Do I set anything to null?
No need to. If your single has finished, you don't have to do anything about it. And there won't be any problems that it is still in CompositeDisposable (even if you call dispose on it). Actually, after the Single is finished, RxJava will dispose the observable itself to safe some memory.
Do I not use Single?
Yes, this is perfect situation to use it. You want to perform a single request, no need to use Observable.
Yes, you should use a disposable. Consider the case when the response is received from your API call, but the context is gone for whatever reason. Then, all your code where you are getting the context and calling methods on it would cause an NPE. Disposing properly of this Single would help you avoid this crash.

Categories

Resources