-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Crash on rotation #3
Comments
Well it was something I expected, because I totally neglect lifecycle handling... until now :) |
OK it will be hard to fix that problem properly. Rxjava and activity/fragment lifecycle have been discussed a lot and as far as I know there is no clean solution. We can easily prevent the crash by making The main problem here is we have rebind the subscription in the newly created activity, after the rotation change. This can be done manually by the user, he unsubscribes when the activity dies and resubscribe when the new activity pops. But I don't like that since it adds too much code for the user. If someone has a good idea, it would be pleasure to hear. |
I had a similar idea building such a library for However: Maybe @dlew , @passsy or @mttkay can share some input here. I think there once was a RxAndroid issue where configuration change has been discussed. However I am not able to find it anymore. |
One way to handle is store observable outside the activity and resubscribe to it when needed. Since the original RxPermissions is bound to the consumer activity, I decided to try out using an invisible activity to handle all this. Seems to be working pretty well and rotation handling is possible when using RxPermissions singleton (or just cached somewhere) bound to an application context. PR coming soon ;] |
as @tbruyelle said it can only be solved with a Singleton (, supporting only a single instance like @eleventigers did) or with a lot of code the user has to put into his activity. I solved such problems in the past by using a presenter (MVP) for my activity which gets passed to the new created Activity after a configuration change with This is just a workaround and no fix for this library. |
IMO, if you want an
If you do the first step, then you can skip having to store the As for why the second is necessary: The core problem is that you want a (You could try to reattach the Of course this comes with all sorts of anti-fun changes to this lib, since users would have to implement FWIW, this is why I ended up not going the |
FWIW, we found a very workable solution by using retained fragments and I guess there's any number of other ways to solve that problem. I believe I I haven't yet heard of a "canonical" way of dealing with the problem. On Thu, Oct 1, 2015 at 3:29 PM Daniel Lew notifications@github.com wrote:
|
My solution using Dagger 2 scopes is quite simple. |
@mttkay I almost used retained fragments, but I found I was actually writing more code than just solving it by hand. |
Wow thank you all for taking your time to give help. About making As @dlew and I said, the main problem is really to rebind the subscription after the death/rebirth of the activity, and I'm more and more convinced this is not possible easily. The main goal of this library is to keep things simple and to provide When you request a permission, the framework starts a activity called WDYT ? |
@tbruyelle I do not think force closing the permission dialog is a legit option. Also you are completely right with pointing out that the retriggering of the permission request is the one breaking the flow we want. At work we ended up using a |
I haven't found time to use this library yet, but from what I see I have to agree with @Gi-lo ... I don't see another easy and reliable way ... We could improve that: Instead of having a However, that would mean that this library would depend on Fragments and FragmentActivity which I really dislike in general. |
Just release a new version 0.2.0 :
That said, the problem about detached subscriber on rotation change is still there. |
I know that this is not good pattern, but what do you think about this: After permission request is send we lock current rotation in activity that send request and ShadowActivity. After we deliver permission repsonse to subscriber we unlock rotation. |
@domsu Yeah this is something to which I already thought, and this is easy to implement with RxJava. We just add a But it can be intrusive, maybe we could make it optional ? As you said it's not a good pattern, but if it shock anyone, I'm ready to implement it, as a recommended option. |
This is not a good idea. There are other configuration changes than the orientation change, such as a language change or docking a hardware keyboard. This lib needs a solution which targets all configuration changes. |
Could the observable not be shared somehow by using one of rx java's share operations? In that case it wouldn't make any difference if the observable gets unsubscribed and resubscribed after screen orientation changes, right? However, in the unlikely case that the activity goes in background before the user grants/denies permission, because the user starts another App (i.e. open a push notification), the hosting activity could be destroyed if the app goes in background ... Hence, I think that saving the instance state in a bundle is the right way to go ... I have something like restoring / recreating an Observable from Bundle and share that observable with multiple subscribers in mind. |
This can't be solved.. If you shoot off another activity you always risk being garbage collected, that means the observable will disappear. You can't solve that as you cant serialize an observable mid stream. |
@andaag Yep I agree. So 2 solutions, put the project to the trash or add hacky trade-offs... I prefer the solution 2 because for me the project brings more good than harm. it doesn't bother me to lock the rotation during the permission request. And if for some rooted devices it's not possible to lock the rotation, the users will have to request the permission twice, this doesn't matter. By explicitly warning the library's users of these trade-offs, I think we can move forward. |
I've been toying with the idea of creating some base Activity/Fragments that would allow you to observe all possible onEvents. With such a tool on hand, this wouldn't be nearly as bad:
It would still require a re-subscription to the Observable, but that's a logical outcome of wanting to have an Observable last longer than the Activity it was created within. |
@dlew Actually with the shadow activity introduced in version 0.2.0, we have such tool ! Nice. About 3, I have no control on the runtime permission request (other than create a new one), I can't cancel or resume it. The permission request is resumed automatically after a configuration change. Anyway, a solution begins to take shape :
That sounds not bad... |
@dlew I would appreciate such an Activity/Fragment where you can observe every lifecycle method. Currently it's impossible to use composition over inheritance for fragments and activities. I saw/build many libs requiring the user to call a delegate in ~10 lifecycle methods to make them work or extend their With a Libs that could profit from this:
This looks like a solution but is not maintained: https://github.com/BoD/android-activitylifecyclecallbacks-compat |
I'll bump this up in my priorities and work out a draft this week. I think there could be a lot of benefit out there for it. |
I once wrote a class called 'LifecycleExtension' where you have one method called 'onLifecycleEventReceived'. I used the RxLifecycleFragment provided in RXLifecycle to provide the correct events. Since that I was able to use composition over inheritence for everything that depends on the lifecycle eg. Butterknife, Tracking and much more. Also those Lifecycle objects are heavily testable. Long story short, a base Activity that has for every callback it implements an observable as well, is something that we should love to use. |
This is a very special case, but it is a case... Might be worth it for some people to use rx anyway though, and know that that case wont be handled very well. |
What we're going to do at my company to use rx for this kind of flow (locationsettings for example). Is to simply return an exception in onError in these cases. So one needs to manually support handling "out of activity" type actions. One can stick a bundle in savedInstanceState and pickup that bundle in onActivityResult, that bundle should contain enough information to continue the rx mid flow (this means you'd need to have multiple chained observables for the original request). Chances are we're gonnna post that bundle/an object on an event bus and pick it up in the same location that we do the original request. To keep the logic for handling it in the same place. |
Example gist : https://gist.github.com/andaag/cb3e3ded2d8d21f02388 |
Interesting, but I'm not sure to understand how things are connected together. |
We wrote code for this for changing location settings, and I've now used the approach for permissions as well. Since we've code the code already written I'll stick it into a library and put it on github probably sometime tomorrow. It's the best workaround to the problem of serialization I've been able to come up with atleast. //Edit : very open for suggestions for names on this as well :p. RxActivityRestart/RxWithSerialization?... |
Nice, please link the library when it's ready. |
https://bintray.com/finnandroid/maven/rxactivityresponse/0.1/view#files I'll push out a library to support google play settings next week. So one can do things like
And get a flow that first asks for location, then asks for gps enabled, then gets a location and returns it. Safe from activity restarts/rotations halfway through. We would have made this a PR, but it's an entirely different approach. With it's own pro's and con's. (Pro's being it'll always work, con being it's more work to support it. Chances are some users would be happy to cover the 95% case). However we'll be using this solution to solve other problems as well. For example getting a picture from the gallery, where your app is fairly likely to be dumped from memory halfway through the process. |
I pushed a PR #14 that aims to fix the issue. I would appreciate if some of you review it. The fix removes some flexibility to the lib but I didn't find anything better. The good news is for clients who request the permission during activity/fragment/view start, nothing change, it will work as these.
Example from the sample app, the click event is transformed to an observable with @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
// com.jakewharton.rxbinding.view.RxView is perfect to represent the event needed
Observable<Object> trigger = RxView.clicks(findViewById(R.id.enableCamera));
RxPermissions.getInstance(this)
.request(trigger, Manifest.permission.CAMERA)
.subscribe(granted -> { |
@andaag Thanks for the help but I think I found a solution which bring less changes on the client side. |
@tbruyelle Hm, that would work, but it puts some pretty big limits on code structure. We have an app with 100+ views (using views instead of fragments as well), and having all of our permission related code in the single activity onCreate isn't really an option. It is considerably easier for those where that is an option though. For just glancing at the code, I'm guessing your using the permission itself as an identifier for the request? That means you can only request a permission from one place in the app. If you want the camera permission in 3 different places your gonna need another identifier. |
+1 |
I think it should work. From the beginning the lib has been designed with concurrent access in focus. For instance, if there's 3 camera permission requests from 3 different places, the same observable is returned to the subscribers. So when the user's answer is caught, all subscribers will receive it. That said, if the requests are done very simultaneously, it won't work as expected since I forgot to use |
I uploaded a snapshot version compile 'com.tbruyelle.rxpermissions:rxpermissions:0.4.0@aar' |
@andaag I tested a little more and it works also if you make the permission request in So you're not limited to |
Right you are... That.. is a better solution. I had a look at it, and overall it looks fine, but since the singleton is a static referanse and you never remove anything from mSubjects it'll leak. I dont see any other choice here but to remove from that on unsubscribe and require clients to implement that. (Or hold a weak ref to subscribers, but that'll require the activity to hold an active referance to it or be GC'd. Cant be an inner referance). Since this is a better approach I'd like to extend this to support onActivityResult as well. Making a seperate issue for that though to keep this on task. See #15 |
When the lib receives the permission request answer, the subject receives |
I'm on my phone right now, so reviewing code is a bit tricky. I'm talking about the case where the user doesn't trigger the action that requires the permission. In that case you are holding the observer which is an inner function holding the activity right? |
Yes you're right, I probably should make |
That'll break if you use it in fragments with the same activity though. I guess it's possible to make a destroy that takes the view / fragment / activity as id and destroy(id), but unsubscribe might be just as clean... Not sure really. |
Also, another idea... Just an idea, I can't really decide whether or not I like it better. If instead of a trigger you hold the subscription. Then you could make the observable a field, and in onClick trigger a .subscribe. Then you dont need RxView for anything. I can't decide if I like this or not, but since I can't decide I figured I'd mention it and you can think it over as well :p. I think it might be better at dealing with special cases, for example if you want to immediately start showing a spinner, then request permission or something like that. |
Also, currently the branch doesn't allow you to click the button more than once. Not an issue with "turn camera on" functionality, but it'll break a whole lot of other flows. |
@andaag Thanks for the report, I noticed that bug last friday. The issue is due to the Indeed, I have an idea of the final fix, I'll publish it today. |
Hey there,
First of all nice work. Sadly I have to say that your solution currently does not work with rotation changes. Simply press "Enable Camera", rotate your device and press "DENY" on the popup. This will result in the following crash:
Do you have any ideas how to tackle this problem?
The text was updated successfully, but these errors were encountered: