How to use idling resource with NavigationView? - android

I am trying to get rid of nasty workarounds in my testing code and properly use idling resources, but there are a lot of pitfalls.
I'm doing an integration test with drawer menu navigation, so I have code like:
onView(withId(R.id.nav_view))
.perform(navigateTo(R.id.my_nav_destination));
Although animations in the developer settings are disabled on the test device in the developer settings this might nonetheless take some time, so there's a need for an idling resource if I do not want to use an ugly Thread.sleep()...
I thought about implementing my own IdlingResource but I don't know how to determine the correct moments to set the resource to idle or not idle. There is an OnNavigationItemSelectedListener in NavigationView which might(!) be useful to determine that the NavigationView is idle again (but I'm not entirely sure if all animations are done if an item is selected), but when to set the NavigationView to not idle?
Is there a way to use an IdlingResource for navigation?

Related

Android feature toggle on UI - searching for design pattern

I am searching for a design pattern i can use so i can feature toggle UI elements.
I have a simple screen that has a horizonal list at the bottom and at the top is a photo. clicking the list updates the photo. Anyway, thats not
the issue. This screen and code already exist. I have a task to change what the list items look like.
I have a task to create a toggle such that the business can test this feature out, if they dont like it they want to revert back to the old design
by simply using a feature toggle.
I dont want to copy all the files and make duplicates with a if else condition on the files if i can avoid it, (if you think its the better way let me know)
i was thinking there is a design pattern something like a strategy pattern i could use to do this more cleanly. even chain of responsilbity could be used.
but anyway, im not sure the best way to do this, how have you all handled feature toggles on UI elements ?
You could use a toggle router, like here.
https://martinfowler.com/articles/feature-toggles.html

How to tackle display tearing during drawer animations

I'm using a 3rd party drawer that allows bottom- and top-edge drawers (https://gist.github.com/patrickfav/6284130 MultipleOrientationSlidingDrawer).
When I animate opening of the drawer, I get display tearing but only under particular circumstances. When the drawer header is clicked, animation is smooth and beautiful.
I have a RecyclerView in the main window. If users click on an entry in the list view, the drawer slides open showing details of the entry selected (click on a track, slide the media controls up from the bottom). Part of the drawer animation is to fade the main window contents to black as the drawer opens. So there's heavy overdraw going on. Oddly, when the animation is triggered from clicking in the RecyclerView, I get tearing of the display during animation (serious flickering, occurring about 2/3 of the way down from the top of the screen).
I'm looking for advice as to how to proceed: how to debug, or theories as to what might be causing flickering of the display during animation.
Here's what I've tried so far.
Initially, I though this was caused by RippleDrawable animations in the RecyclerView entries (both on the press and an activition change that is used to indicate selection. To prevent this, I've tried to cancel the ripple animations by calling recylerView.jumpDrawablesToCurrentState() at various points (start of animation, as well as during every animation update). As far as I can tell, the RippleDrawable animations have been effectively cancelled. But the display ripping still occurs. Things are a bit complicated. State change animations don't get triggered until after a dispatch of some kind occurs; but the call to startAnimation also doesn't appear to occur until after drawable state changes are complete. So I think I've done this right. Just in case, I've tried calling recyclerView.jumpDrawablesToCurrentState during each animation pass, which certinly should cancel the drawable animations. Still no joy.
I have deferred responses to the click itself until the drawer open animation completes, so there are no major background or foreground operations going on while the animation occurs. Also no audio running, no service activity to speak of. Profiling indicates that there's no code running that isn't related to drawing while the animation runs.
Since everything happens relatively quickly, it's hard to tell exactly what's happening, other than that there's serious flickering going on. Since there's an animation involved, there's no way to break to the debugger in the middle of the animation to see what's going on. Once I break to the debugger, the next redraw will be fully-open state, since the animation positions derive from the current device uptimeMillis().
If I profile the animation, I see an awful lot of text layout operations, which kind of suggest that the recycler view entries might be constantly performing layout during the animation.. Which doesn't really make sense. There's no reason I can think of for layout to occur. It could just be that the text calls are related to onDraw operation. But I mention this because what I did see in profiling seems a bit odd. The majority of CPU time during the animation seems to be spent performing text measurement operations, presumably for content in the RecyclerView entries.
There is heavy overdraw occurring. The drawer itself has a large bitmap containing album artwork, And it's possible that I may have stacked up semi-opaque layers and backgrounds. e.g. the RippleDrawable backgrounds on individual entries in the RecyclerView could very well cause a complete overdraw pass for most of the screen. In addition, there is a complete overdraw pass for the view that dims out the background content as the drawer animates into open position. That being said, the drawer animates beautifully when the animation is triggered by clicking the header of the drawer. Just not when the click that starts the animation comes from the RecyclerView. So I don't think overdraw is really the problem.
For what its worth, the location of display ripping seems to be independent of where the selection and press occurs. Clicking first or last entries in the RecyclerView doesn't affect where the display-ripping occurs.
ADB shell dumpsys SurfaceFlinger indicates that hardware composition is being performed on three layers (status bar, navigaton bar, and main activity). So the drawer itself is not using SurfaceFlinger composition (which would, if it were happening at least point in the general direction of what's causing tearing of the display). However, I can't really get the SurfaceFlinger state while the animation is running. It's possible that hardware compositing is being used during the animation. It sure looks like display ripping, and I can't think of why ripping should occur if SurfaceFLinger isn't doing the compositing.
I'm completely mystified as to what the problem might be, or approaches to try and debug this. Any suggestions as to general debugging approach, or suggestions as to what the cause of the problem might would be much appreciated.
On what layer is the described tearing happening? Is it on the drawer itself, or on the layout behind the drawer?
Either way, I have a couple of suggestions:
Overdrawing can be glitchy at times, especially with complicated layouts - I have run into issues with it specifically on GoogleMaps Fragments. Something that resolved this in my case was creating a simple transparent overlay to cast the overdraw on, like so:
<FrameLayout for drawer....>
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/transparent" />
<LinearLayout w/ fragments etc..../>
</FrameLayout>
Drawer Animations are notoriously bad for their intended purpose... they will lag like crazy during layout transitions. I know you said you made sure the drawer animation finished before doing anything, but that is something worth double-checking. I personally put all of my navigation code in the OnDrawerClosed() method for this reason, and use the itemSelected() code to set a flag and close the drawer.

Activity Return Transitions - Shared Elements - Killed Activity

Library used: appcompat-v7:22.2.1,design:22.2.1
Theme used:
Devices/Android versions reproduced on: Nexus 6
Issue: Return activity quickly redraws/appears then fades in with desired behaviour, only with "Don't keep activities alive".
I am wondering if this is a bug or expected behaviour. I have a very simple setup. Activity A contains a toolbar wrapped in an AppbarLayout and CoorindinatorLayout. The toolbar contains a Cardview and a TextView. Upon click of the TextView, Activity A launches Activity B. I am using shared elements and passing them through as Option's via ActivityCompat.StartActivity(bundle, options);
My shared elements work perfectly, even after device rotation. After reading about how I can PostPoneEnterTransition and combo it up with PreDrawListeners I am able to successfully achieve the desired transition even after rotation. My actual activity contains a Viewpager / TabLayout and 2+ fragments but for simplicity sake, I've stripped it back in the video as well as to see if something else was causing this issue.
While dealing with rotation and postponing of the enter transition back to Activity A, I decided to open developer options and check "Don't keep activities alive". The video depicts the app running with that option enabled. If you look closely, you can see upon return to Activity A, it is completely drawn and hidden very quickly and then the fade in occurs as well as the shared element transition.
I've also excluded the navigation bar and status bar in the animations so that I don't see those flicker (redraw redundantly).
My questions are:
Is this a bug, or am I missing a step in order to prevent this.
Why would the app/transitions behave differently with "Don't keep activities alive" vs a plain old device rotation (destroy/recreate).
I've noticed by playing around with some google apps, this behaviour does not occur, or at least that I could find. Is there a way to concretely check if the activity I am returning too is "completely destroyed" so I can cancel the animation? Or do something different?
I can include specifics and code samples if required but my setup is very simple, and reflects a bunch of boilerplate examples from the Android documentation / Stack-overflow.
Sorry I meant to respond to this earlier. What I ended up doing was recreating the example in a completely fresh project following code samples and tutorials as best I could. First making it work with a single image view, and then of course adding my custom layout which was a floating search bar. Everything worked as expected. I went back and reviewed my actual project source (which was littered with different attempts and commented out code while trying to debug this issue) and cleaned it up. I can't say for sure, but I believe it came down to two possible issues:
"Unless you do something unusual..." - Most likely I "was" doing something unusual by the time I created this issue do to my debugging efforts and lack of full comprehension of the shared elements transition framework and lifecycle.
I think what was happening was the shared element transition was failing do to views not being mapped properly. I was excluding the statusBarBackground inside a transition defined in XML. My statusBarBackground was set to transparent so that I had the nice overlay effect for an expanded drawer layout. I found out that while trying to add the statusbarbackground as a shared element via code, the view was actually null resulting in a crash (NPE). As well as I had set a background color (instead of transparent) to my drawer layout. I can't say for sure, but a combination of these mistakes lead to the strange behaviour.
To conclude, I would say that this issue should be closed and everything is working as intended. It would be nice to get a little more insight on handling a transparent status bar as a shared element.
Is this a bug, or am I missing a step in order to prevent this?
No. Everything is working as intended.
Why would the app/transitions behave differently with "Don't keep activities alive" vs a plain old device rotation (destroy/recreate)?
It doesn't. When everything is setup proper and your timing and mapping of shared elements is correct, "Don't keep activities alive" is a concrete way to test your transitions against configuration changes.
I've noticed by playing around with some google apps, this behaviour does not occur, or at least that I could find. Is there a way to concretely check if the activity I am returning too is "completely destroyed" so I can cancel the animation? Or do something different?
This is because the Google dev's did it right :)
For anyone struggling with shared elements, here is a bit of advice.
Start small. Use a single view first and confirm you are getting the correct behaviour in all circumstances, even after rotation and config changes, then you can add complexity.
Use SharedElementCallback to debug your transitions. You can check which views are mapped, which view failed etc.

Navigation hierarchy on Android?

I've recently been looking into the navigation system that Android uses with as intention to port my iOS app that uses an UITabBarController containing multiple UINavigationControllers. To replace the tab bar (which is not available on Android) I settled on using the built in DrawerLayout.
From what I've read, navigation in Android is generally done by creating an Intent, providing it with extras and then just replacing the current activity. This automatically makes sure the back button works, and optionally the back button in the top left if enabled.
However, I am not sure how to implement this way of navigation with the navigation drawer. The tutorial tells me to create a DrawerLayout containing a FrameLayout and a ListLayout where the FrameLayout will contain the actual application and the ListLayout will contain the navigation. This would mean that when I use the method described above to "navigate", it would replace the activity and thus removing the drawer.
What would be the best way to implement what I want (basic navigation with back button support while maintaining a global drawer navigation menu)? The possible options I can come up with is always keeping the same activity and dynamically replacing the FrameLayout, but that would mean a lot of boilerplate to render and possibly a hack to support the back button (and there would be no animations :(). The other option would be to just render the drawer on every activity (via subclassing or something), but that would mean that if the user navigates a lot the back button "stack" would become quite large.
I have tried to explain what I need in as much detail as possible, but it is quite hard to explain the concept. Basically, I want something similar to the UINavigationControllers in the UITabBarController.
You can either have one Activity with one NavigationDrawer and present the user with different views by switching Fragments back and forth within that one Activity. You would use the FragmentManager to switch between different Fragments.
Or you can use multiple Activities that all have a NavigationDrawer.
Second option might sound more difficult but it really isn't. You create a base Activity that all your Activities inherit from and all let them have their own NavigationDrawer, no problem.
Sure there's something in between or something completely different, but that's the most straightforward approaches I can think of.
The tutorial you've probably used (the one with the planets) is imho a bit misleading because it assumes a very basic app structure. If you have only little different 'screens' that might work, for a very complex application it's not suitable (again, in my opinion).
I've always opted for the second option because handling the navigation / backstack is just easier with Activities / Intents.
There's loads of different flags that you can set to your Intent to influence their navigation behaviour.
Also see this and that documentation. These documents might have been written when the NavigationDrawer pattern was not all that common but they are still useful.

Android, SlidingDrawer

I've noticed that animateOpen(), animateClose() and animateToggle() are doing the same function, meaning that any one of them can replace the others.
The only difference that I noticed was that the speed of the animation varies from one method to the other as follows:
animateOpen(), the animation while opening is faster than the animation while closing.
animateClose(), the animation while closing is faster than the animation while opening.
animateToggle(), both speeds are equal.
So, my question is:
Am I missing something? or do I just have to check isOpened() before using any of them?
I'm asking this question because my problem raised when I wrote animateClose() somewhere, thinking that if the sliding drawer is already closed then no action will be taken, but I found out that it behaves exactly the same as animateToggle().
Confirmed, on two different devices.
animateClose() called on a drawer that is already closed will sometimes animate the drawer to opened.
animateOpen() called on a drawer that is already opened will always (?) animate the drawer to closed.
The immediate functions (close and open) seem to work as you would expect.
Given this, I would suggest subclassing the SlidingDrawer and overriding the 5 methods that open or close the drawer. Using a few member variable booleans, you should be able to determine the real state of the drawer and call (or not call) the appropriate superclass method, updating your state accordingly.
(It might also be necessary to implement the OnDrawerXxxListeners to keep your state correct; my drawer is only opened and closed programmatically, not using the "handle", so I didn't test that.)
Edit to add: The nice thing about doing this is that you can add an isOpening() and isClosing() based on your subclass's state plus the existing isMoving() method.
animateOpen, animateclose and animateToggle public methods will make the slide open,close or toggle with animaiton respectively. The methods have to be compared with open, close which will open and close the slider without animation. The speed of animation should not change in default implementation.

Categories

Resources