Jetpack Compose BackPressHandler fails to intercept back presses after onResume - android

In my Jetpack Compose project I'm using BackPressHandler from Android examples. Everything works as expected until I pause and resume the app. At this point BackPressHandler is not intercepting back button anymore and navigation just defaults to regular back press behaviour.
Was able to recreate the issue in a demo project, please see code example:
https://gist.github.com/vitalnik/2a444c20cb9f370c405ee1ddb86d3e66
Thanks!

No need to use BackPressHandler anymore, since that article was written, system BackHandler was added, so you can switch. It doesn't have such an issue.

Hi like #Philip explained use BackHandler, it highjacks your backpress when it's enabled.
one example I use this is with combination of bottom sheet state isExpanded so when its true backpress could dismiss the bottomSheet and also get disabled itself, try to use this kind of combination.
val scope = rememberCoroutineScope()
BackHandler(enabled = bottomSheetScaffoldState.bottomSheetState.isExpanded){
scope.launch{
bottomSheetScaffoldState.bottomSheetState.collapse()
}
}

Related

how to make jetpack compose lifecycle compatible with Fragment life cycles?

My app previous was made using Fragments as pages. Now i'm integrating Jetpack Compose into my app. The pages are like this:
HomePageFragment -> the home page made with traditional XML
ItemListPageFragment -> a fragment wraps ComposeView that everything is done by Compose
ItemDetailPageFragment -> item detail page made with traditional XML
Here are the effects I want to achieve:
When entering ItemListPage, show loading then a list of items. then user scroll down a bit, select an item, jump to ItemDetailPage
When go back from ItemDetailPage to ItemListPage, UI shouldn't reload, but stays at where it was left with.
My ItemListPage is like this:
#Composable
fun Content(){
// third party lib that kind of like useSWR for React
val states = useRequest(fetcher = {....})
if(states.error) {...}
if(states.loading) {....}
// render list of items
....
}
right now it triggers fetch and loading UI when I go back from ItemDetailPage to ItemListPage.
I think the issue is Fragment destroy views when "page-out". Then when it is "back-to" page, it creates new ComposeView and triggered onLoad event.
Use ViewModel
OR
Use rememberSavable so that there won't be change even if there are any configuration changes

Android navigation component display multiple DialogFragments in a row

Hi folks I need to throw up a series of DialogFragments one after another using the navigation component. I have encountered some pretty unusual system behavior which looks like a race condition. I show the dialogs with a global action after an item is clicked, and use the fragment result api to determine if another one should be shown.
I am using a custom layout so there's no positive / negative listeners etc., and my own continue / cancel buttons send a result back to the host fragment.
ItemsFragment.kt:
navController.navigate(RegisterItemsDirections.openModsDialog(item.id, 0))
fragment.setFragmentResultListener(ItemsFragment.MODIFIERS_REQUEST) { key, bundle ->
//kill the current dialog
navController.navigateUp()
//some logic to determine if we need another dialog...
if(needAnotherDialog){
//navigate to the next one
navController.navigate(RegisterItemsDirections.openModsDialog(item.id, lastModGroupSelectionIndex + 1))
}
}
ModsDialogFragment.kt, when the user clicks "continue"
setFragmentResult(MODIFIERS_REQUEST, bundleOf(MODIFIERS_RESULT to selectedMods))
So the issue only appears on 3rd or more dialogs on my devices, I can see that the 1st and 2nd dialogs are completely destroyed and detached. When it displays the 3rd one, the first one attaches itself again, and appears beneath the 3rd one which I can't explain.
I've tried:
Popping the back stack in the global action's nav xml
Navigating up or dismissing the dialog fragment in the dialog itself (before calling setFragmentResult), which is the most logical place to put it
Popping the backstack instead of navigating up, basically the same thing in this case
When I don't dismiss / nav up any of the dialogs and allow them all to just stack, there's no issue. When I delay the navigation by 500ms there is no issue. So navving up and then immediately navigating to another instance of the dialog are fighting with eachother producing very strange results. What could be the solution here that doesn't involve a random delay?
Navigation version is 2.3.3 and I'm having a lot of trouble trying to update it due to AGP upgrades being incompatible with a jar I need so I'm not sure if this has been fixed.
I figured out the issue, it's down to dumb copy pasting.
I took the donut tracker sample code and made it stack dialogs in the same way and there was no issue.
The difference between the two was I am subclassing DialogFragment and for some unknown reason doing this:
override fun show(manager: FragmentManager, tag: String?) {
val transaction = manager.beginTransaction()
val prevFragment = manager.findFragmentByTag(tag)
if (prevFragment != null) {
transaction.remove(prevFragment)
}
transaction.addToBackStack(null)
show(transaction, tag)
}
This code predates the Nav component library I believe, and it completely f***s with the fragment manager used by the navigation component. So the moral of the story is to not do bizarre things in super classes, or better yet not super class at all.

Perform Segue From viewDidLoad()

I am new to iOS. I have a very similar requirement like my working Android project. The requirement is that in my LoginActivity onCreate(), I am checking for some condition and if it is true then I am launching my next Activity using an Intent.
I am trying to perform the same functionality form my iOS app. In my LoginViewController viewDidLoad(), after checking for some condition, I am calling [self performSegueWithIdentifier:#"myNextControllerSegue" sender:nil];.
But, my ViewController is not changing to next view controller. Any help would be appreciated.
Need to perform segue at right place.you are tried to loading another view before first one in hierarchy.So viewDidAppear is called you have a fully loaded view to modify.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self performSegueWithIdentifier:#"myNextControllerSegue" sender:nil];
}
To remove that flickering, just hide the view in your viewWillApear method.
otherwise as quick search you can do that into main thread also like below
dispatch_async(dispatch_get_main_queue(), { () -> Void in
//perform segue
})
viewDidLoad: is not used for segue performing. You should call the segue in viewDidAppear:, where the view structure is already established.
On your Storyboard, click on your view controller, go to the "Connections inspector" part, and in "Triggered segues", drag the "+" button to the controller you want to reach.
Now click on your segue and go on "Attributes inspector" part, and set "myNextControllerSegue" for identifier.

BackAndroid hardwareBackPress listener stops working after returning to initial route

I have a React Native app that is on 0.18, and am trying to implement back button functionality for Android. I have the following code in my index.android.js:
let navigator;
BackAndroid.addEventListener('hardwareBackPress', () => {
if (navigator && navigator.getCurrentRoutes().length > 1) {
navigator.pop();
return true;
}
return false;
});
And in my render:
<Navigator
ref={(nav) => { navigator = nav; }}
...
The back button works if I go forward any number of routes, and I can then go back any number of routes to the initial route. However, after going back to the initial route, the back button stops working until I reload JS or otherwise restart the app.
Has anyone else encountered this and what is the solution?
Edit: I've tested on 0.21, 0.22, and 0.23-rc3 and am still having this issue in the latest release.
It appears the issue was that, in other components of the app, we had added event handlers to handle changing layouts on opening the keyboard, and on unmounting these components, we were calling DeviceEventEmitter.removeAllListeners(). Contrary to intuition, this call removes all listeners globally, regardless of the instantiating component, and applies even to listeners instantiated on BackAndroid instead of on DeviceEventEmitter. So, after going back from a component that had a keyboard-toggled layout, the hardwareBackPress listener was also removed and the back button stopped working.
Making the removeAllListeners calls more specific e.g. DeviceEventEmitter.removeAllListeners('keyboardWillShow'); resolved this issue.

How to show "up" / "back" button in Xamarin.Forms?

I am trying to work out how to show the "up" arrow in Xamarin.Forms without a pushing a page onto the stack. I.E. I just want to perform an action when the back button is pressed. I am completely stuck on this so any help would be appreciated.
I have tried creating a custom renderer which handles a view property called DisplayHomeAsBack. Which in the renderer calls the following:
FormsAppCompatActivity context = ((FormsAppCompatActivity)Forms.Context);
Android.Support.V7.App.ActionBar actionBar = context.SupportActionBar;
if (actionBar != null)
{
actionBar.SetDisplayHomeAsUpEnabled(element.DisplayHomeAsBack);
}
Unfortunately it seems this does absolutely nothing, even though all online tutorials and stackoverflow question for android suggest this method.
The plan is that I can then use the "OnBackButtonPressed" override in MasterDetailPage, which should allow me to perform this action. Unfortunately displaying the back button has been the larger hurdle so far!
Any idea of a better way to do this or how I can get the current mechanism to work?
EDIT
I have created a project and uploaded it to this question on the Xamarin support forums, if it helps.
http://forums.xamarin.com/discussion/comment/186330#Comment_186330
Sorry to keep you waiting so long!
Warning that I did not actually run this code and changed it from my own so I would be surprised if it worked perfectly without some changes.
So below should add a back button where there was not one before (so like when there is not really a page to go back to) and then we will add a custom action to perform when it gets pressed.
I would suggest you push a new page onto the stack without using animation so it is transparent to the user and also makes all of this much simpler, but if you absolutely do not want to do that, the below method should work.
MainActivity:
//Use this to subscribe to the event which will create the back button
public override bool OnCreateOptionsMenu(IMenu menu) {
if(menu != null && App.AppMasterPage != null) { //You will need this to make sure you are on your MasterDetailPage, just store a global reference to it in the App class or where ever
Xamarin.Forms.MessagingCenter.Unsubscribe<string>(this, "CreateBackButton");
Xamarin.Forms.MessagingCenter.Subscribe<string>(this, "CreateBackButton", stringWeWillNotUse => { //Use this to subscribe to the event that creates the back button, then when you want the back button to show you just run Xamarin.Forms.MessagingCenter.Send<string>(this, "CreateBackButton")
ActionBar.DisplayOptions = ActionBarDisplayOptions.ShowTitle | ActionBarDisplayOptions.ShowHome | ActionBarDisplayOptions.UseLogo | ActionBarDisplayOptions.HomeAsUp; //You may need to play with these options to get it working but the important one is 'HomeAsUp' which should add the back button
});
} else {
Xamarin.Forms.MessagingCenter.Unsubscribe<string>(this, "CreateBackButton");
}
return base.OnCreateOptionsMenu(menu);
}
Now the next step is do do a custom action when it is pressed. I think you can either override OnBackPressed() or OnOptionsItemSelected() in MainActivity or maybe you can override the MasterDetailPage method. I am not sure.
Which ever one works for you, inside of that override, I would simply check to see if you are on your App.AppMasterPage like we did above, and if so, send a MessagingCenter message which your App.AppMasterPage has already subscribed to in order for it to handle the custom action.
If you get stuck let me know!
I know it sounds like a bit of a hack, but the best "solution" I have found so far is to add a page behind the current page (behind the root) so it is not visible. Then when the user presses the back button, handle it by removing that page.

Categories

Resources