setSoftInputMode() in a ViewPager - unexpected behavior - android

I have 2 fragments in a ViewPager and I want the window to adjust differently to the soft keyboard on the 2nd fragment. Here's what I'm trying:
#Override
public void onPageSelected(int position) {
if(position == 1){ // desired for 2nd fragment
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
} else { // desired for 1st fragment
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED);
}
}
Observed behavior:
Enter 1st fragment and default softInputMode is working, as expected.
Swipe to 2nd fragment and breakpoint shows that the softInputMode should be set to ADJUST_NOTHING, but everything still behaves like the default.
Swipe back to 1st fragment and it behaves with ADJUST_NOTHING.
Swiping back and forth now reveals both fragments to behave like ADJUST_NOTHING, even though breakpoints show these calls are being made.
To top it off, I can switch fragments all I want and the input mode will behave as default until I pull up the soft keyboard. Then it starts its migration toward ADJUST_NOTHING. I'm quite baffled.
I don't have any relevant flags in the manifest, although in my Activity onCreate() I do set the input mode to SOFT_INPUT_STATE_ALWAYS_HIDDEN.

The solution I found works well enough, although I hope there's a smoother way out there somewhere. I did two things.
First, I stopped using SOFT_INPUT_ADJUST_UNSPECIFIEDas my "default" input state because the WindowManager seems to treat changes to and from this state a bit differently. 'SOFT_INPUT_ADJUST_RESIZE' gives the behavior I desire, so I changed my method to
#Override
public void onPageSelected(int position) {
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
);
hide_keyboard(activity);
if(position == 1){ // desired for 2nd fragment
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
} else { // desired for 1st fragment
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
}
}
This fixed the "getting stuck in a certain input mode" issue. Still, I wasn't getting the input mode I wanted on my 2nd fragment, so I added a callto set the input mode right before launching the DialogFragments with text input fields from my 2nd fragment.
getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); // fragment behind doesn't get readjusted from keyboard.
editCatFragment.show(getActivity().getSupportFragmentManager(), "editCatFrag");
This actually doesn't guarantee the mode I want the first time the fragment is launched, but makes it work the 2nd time which, due to how my app is designed, actually works out okay.
Hopefully this helps someone and hopefully there's a better way of solving this problem. Thanks!

Related

Wrong fragment shown after return transition

I add a fragment with a shared element transition like so
currentFragment.setEnterTransition(TransitionInflater.from(context).inflateTransition(android.R.transition.fade));
currentFragment.setExitTransition(TransitionInflater.from(context).inflateTransition(android.R.transition.fade));
Transition transition = TransitionInflater.from(context).inflateTransition(android.R.transition.slide_right);
transition.setDuration(context.getResources().getInteger(R.integer.fragment_transition_duration));
and then
targetFragment.setSharedElementEnterTransition(TransitionInflater.from(context).inflateTransition(R.transition.change_image_transform));
targetFragment.setEnterTransition(TransitionInflater.from(context).inflateTransition(android.R.transition.fade));
targetFragment.setReturnTransition(transition);
The issue I'm seeing is that once I've navigated to several of these fragments in a row, if I then tap the back button - calling the default onBackPressed() - repeatedly and very quickly, the activity shows the wrong fragment as visible. When I tap on the screen, the click events go to the correct fragment (maybe beneath the visible fragment), but I cannot see that correct fragment.
If I tap the back button more slowly, I get the correct behavior. Has anyone ran into a scenario like this before?
Edit : this is what FragmentActivity is doing
/**
* Take care of popping the fragment back stack or finishing the activity
* as appropriate.
*/
public void onBackPressed() {
if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
supportFinishAfterTransition();
}
}
Another thing to mention is that when I remove the Return Transition I do not have this problem.
Ok, I found the answer if anyone is interested. I don't know why this works, but it seems to fix my issue.
If you have a fragment which has a return transition set on it, then in your onDestroyView() call the method setReturningTransition(null).

Handle back action for multiple view

I have an activity which have multiple piece of UI panel(you can think them as view in android), these panels will be invisible by default.
Now when user trigger action1, PanelA will display, when trigger action2, PanelB will display(at different location of the screen).
Both PanelA and PanelB is visible at the moment, now when user hit the back menu, the PanelB should disappear, and PanelA should disappear when hit the back menu again.
At first, I use View to hold different panels, however I found it is difficult to keep the state consist, for example, the activity will be a little different between PanelA and PanelB.
Then I found the fragment, however after I tried and tested, I found that the addTobackStack() can not apply to my us-case, since the PanelA and PanelB are at different location, android can not save their state by default.
So I wonder if there is any other solution for my requirement?
You need to manually handle this scenario inside onBackPressed() method of an Activity.
For instance -
#Override
public void onBackPressed() {
if (panelB.isOpened()) {
panelB.close()
} else if (panelA.isOpened()) {
panelA.close()
} else {
super.onBackPressed();
}
}
When panelB is opened, it will close only panelB and wont do anything else. Same goes for panelA, if its opened and when both the panel are closed then it will exit the app like normal.
I highly recommend to use DialogFragments here as you can call show() and dismiss() any point of time on that and they can handle custom views pretty well.
Hope it helps.

How to avoid multiple instances of fragments in Activity after app is killed and resumed?

I have an app with a Home screen that has 2 fragments (for now) and a navigation drawer. Currently I load the fragment A (Explore) on startup and load fragment B when clicked. From then on, I show and hide fragments. It's faster than recreating fragments on every click and my fragment A takes some time to load.
I've noticed that when I go to fragment B and go to another activity (let's call it activity 2) from there and leave the app and wait for it to be killed (or do something crazy like change the device language), and then come back to the same activity, it's still there. When I press back to go back to fragment B, sometimes (50% of times) the fragment B is drawn over fragment A. On clicking fragment A in the drawer, fragment A appears fine, but on clicking fragment B, there's another instance of fragment A and on top of that fragment B.
I've spent more than 2 days on this problem and got nowhere.
Here's my code for selecting the fragment:
private void selectItem(int position, boolean addExploreFragment) {
Log.d(tag, "selectItem: " + position);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
//add explore fragment - this is called on app startup, but also when the app is killed and resumed which results in 2 explore fragments
if (addExploreFragment){
fragmentTransaction.replace(R.id.content_frame, mExploreFragment, EXPLORE_FRAGMENT_TAG);
Log.d(tag, "Replaced frame and added "+ mFragmentTags[position]);
} else {
//add fragment for the first time
if (getSupportFragmentManager().findFragmentByTag(mFragmentTags[position]) == null && position != 0) {
fragmentTransaction.add(R.id.content_frame, mFragments[position], mFragmentTags[position]);
Log.d(tag, "Added Fragment: "+ mFragmentTags[position]);
}
//shows and hides fragments
for (int i = 0; i < mFragments.length; i++) {
if (i == position) {
fragmentTransaction.show(mFragments[i]);
Log.d(tag, "Showing Fragment: "+ mFragmentTags[i]);
} else {
if (getSupportFragmentManager().findFragmentByTag(mFragmentTags[i]) != null) {
fragmentTransaction.hide(mFragments[i]);
Log.d(tag, "Hid Fragment: "+ mFragmentTags[i]);
}
}
}
}
fragmentTransaction.commit();
//not null check for calling selectItem(0) before loading the drawer
if (mDrawerList != null){
mDrawerList.setItemChecked(position, true);
}
}
I know for sure, the explore fragment is getting created twice and the two instances behave independently of each other (just sharing).
I'm lost what to do next. This is an issue which can be reproduced very easily on low end devices but on a device like Nexus 4 (my test device), the issue can be reproduced by changing the device language.
Has anyone got any ideas about this? Basically if the addExploreFragment block doesn't get called when there is already an exploreFragment, this issue could be solved, I think, but I've been unable to do so. Also, I tried removing all the fragments and then adding the exploreFragment but same thing happens (50% of times).
Thanks! and sorry for the long post, I felt I should share all the details.
Update: When I change the device language and come back to the app on Activity 2 and go back to Home activity, it has the fragment B open which is good, but fragment A get recreated because it's a heavy fragment and the system probably removed it from memory. Again, that's ok that it gets recreated IF it got removed by the system but why does it get recreated when it's not removed. I believe it's something with my code, on every 2nd attempt (without closing the app) this happens, 2 instances of the heavy fragment A. Out of ideas.
But shouldn't fragmentTransaction.replace remove all the previously added fragments and then add exploreFragment. It's not working like that. Neither fragment A nor Fragment B are getting removed.
I found out something new and rather odd to me. When you use fragmentTransaction.add, the listeners you have, like DrawerItemClickListener, on the previous fragment, are still active. And this is even if you use fragmentTransaction.commit.
So...I suspect when the add method is used, you actually clicked on another hidden button or hidden UI that has an event listener on the previous fragment. I don't like this of course and the effect may be very confusing. Yes, this happened to me and I didn't understand why for a while.
For now, I think the easiest code fix would be to use the replace method instead of add. The replace() makes listeners inactive. If it works, then you can make a better/elegant fix.
Let me know what happens....
I started to notice your post
when I go to fragment B and go to another activity
When you interact or start another Activity, you start a new set of Fragments. Look at this Google webpage # Fragments Lifecycle.
For clarification of my claim, there is a quote saying
A fragment must always be embedded in an activity and the fragment's
lifecycle is directly affected by the host activity's lifecycle.
You might as well read few paragraphs of it, at least.
I am not sure what your solution should be. Perhaps make the fragments distinctive, different and clear between the two Activities you have.

Which method handles the changing appearance of a Fragment UI?

Basically, I have a list of detail fragments and each one represents a list of peers of that phone. When one of these DeviceDetails is tapped on, a selection of buttons and text appears. Which of these buttons and text appears depends on the status of the phone; it is either connected to the phone being used, or it is available for connection.
I currently use the fragment's onCreateView to make 3 buttons appear. 1 of these buttons should appear every time, and the other 2 alternate depending on the connected/available state.
I am trying to figure out which overridden fragment method should handle the changing UI's. It should just be a case of if statements (or maybe switch statements?) but I am not sure where to place these?
Well if let's say you have an Activity and it is hosting all these Fragments.
I assume there is some event that triggers this state to happen, maybe in that Activity
public void onSomeEventThatICareAbout(EventDetails deets) {
Fragment fragment = getFragmentManager().findFragmentById(R.id.my_fragment_with_buttons);
if (fragment != null) {
((MyButtonsFragment)).disableButtons(deets);
}
}
Basically just treat the fragment like any other component and call methods on it based on events like normal, whether it is an onClick(), a AsyncTask callback, or whatever. Just call the function right on the fragment.
Define your own way for your fragment to do what you want,
public void disableButtons(EventDetails deets) {
View view = getView();
view.findViewById(R.id.button1).setEnabled(false);
view.findViewById(R.id.button2).setEnabled(false);
}

How do I get the back button to go to a certain Fragment in horizontal scrolling? Exits app right now

the title pretty much explains it.
I have horizontal scrolling set up, the first screen has buttons to the other Fragments as well as the whole horizontal scroll system. What I would like is for the user to be able to press the back button when on one of these fragments and for the app to return to the first screen with all the buttons.
From there I want the back button to be an AlertDialog asking the user if they would like to exit the app. At the moment this is what is happening (On all Fragments when you press the back button the AlertDialog I created pops up).
I've looked into Fragment transactions and "addToBackStack()" but I don't know how to implement it. I've looked at the dev guide and certain questions on this site but getting one or two lines of code doesn't help in implementing it.
I have a FragmentActivity with a FragmentPagerAdapter set up and each Fragment has its own Java file. I have 5 Fragments that are all called in the FragmentActivity and FragmentPagerAdapter.
I don't think I need to show you guys any of my code for the moment since it's all set up in the normal manner. Please let me know if you do though.
The bit of code I found on other questions and one in particular was the following:
FragmentTransaction tx = fragmentManager.beginTransation();
tx.replace( R.id.fragment, new MyFragment() ).addToBackStack( "tag" ).commit();
It's a bit hard to go on just that though.
I would really appreciate your help.
EDIT: my code removed - wasn't needed.
If you use the ViewPager from your question which I answered earlier and you want to come back to the first fragment of the ViewPager when the user presses the BACK button then override the onBackPressed method like this:
#Override
public void onBackPressed() {
if (getSupportFragmentManager().findFragmentByTag("outDialog") != null
&& ((DialogFragment) getSupportFragmentManager()
.findFragmentByTag("outDialog")).isVisible()) {
// we have the out dialog visible and the user clicked back so let
// the
// normal events happen
super.onBackPressed();
return;
}
int currentPosition = mViewPager.getCurrentItem();
if (currentPosition != 0) {
// if the page the ViewPager shows isn't the first one then move it
// to the first one
mViewPager.setCurrentItem(0);
} else {
// we are at the first position already and the user wants out, so
// annoy him with a dialog that asks him once again if he wants out.
DialogFragment askHim = new DialogFragment();
askHim.show(getSupportFragmentManager(), "outDialog");
// in the dialog listener, if the user presses ok, finish the activity
}
}
did you try to override Activity.onBackPressed() ?
I used addToBackStack() and it works well, but I have no idea whether it works with PagerAdapter.
I think you can override Activity.onBackPressed() and in that method, you can check whether current page is front page or not and do whatever job you want to do.
Here are pseudo code that I think.
public void onBackPressed() {
if( pager.getCurrentPage() == 0 ) { //I'm not sure this method exists or not. just example. :-)
//exit code here
} else {
// show first page
}
}

Categories

Resources