How to determine when a fragment is visible to the user - android

my app has one activity called MainActivity.
MainActivity loads 3 fragments, all added to back stack. The first one is automatically opened and is ListFragment. If I click on an item in ListFragment the DetailFragment is created. Inside DetailF I can click the edit button and EditFragment is opened. So the backstack is:
ListFragment
DetailFragment
EditFragment
Inside EditF one can change informations about the object that is being displayed in DetailFragment. Inside the onPause of EditF I save the chanegs
#Override
public void onPause() {
super.onPause();
mTask.setTitle(mTitleEditText.getText().toString());
mTask.setSummary(mSummaryEditText.getText().toString());
}
So when I hit the back button the chanegs are successfully saved (I see that from the logs). So DetailF appears but the changes are not shown: that's becuase DetailF has been previously loaded with the beggining data. Since the onresume doesn't get called when DetailF is visible I can't tell the fragment to update the data.
If I hit the back button another time I go back to the ListF and the updated data is also not shown.
This is how I create the fragemnts:
in MainActivity.java
private void launchListFragment(){
getSupportFragmentManager().popBackStack ("detail", FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fragment newDetail = ListFragment.newInstance();
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, newDetail, "LIST_F_TAG")
.commit();
}
in ListFragment:
Task taskToSend = mViewAdapter.getList().get(adapterPosition);
if(getActivity() != null){
Fragment newDetail = DetailFragment.newInstance(taskToSend);
getActivity().getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, newDetail)
.addToBackStack("detail")
.commit();
}
in DetailFragment:
private void launchEditFragment(Task task){
if(getActivity() != null){
Fragment newDetail = EditFragment.newInstance(task);
getActivity().getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, newDetail)
.addToBackStack("edit")
.commit();
}
}
I want that once I update the mTask data in EditFragment all other fragments display the updated data. Thanks.

Related

Exiting from Activity with only fragments

My app contains one empty activity and a couple of fragments. The onCreate of the activity replaces the empty view in activity_main.xml with a MainFragment that contains some buttons. Each button launches a separate fragment, and user can navigate from one fragment to another, etc.
On the press of back key, the current fragment correctly gets replaced with the previous fragment, until you get to the MainFragment. When user presses back from MainFragment, it hides the main fragment and you see the white empty background of the main activity. But I want to exit from the activity at this point, as that would be the sensible behaviour.
I am able to achieve this by calling super.onBackPressed() for a second time from onBackPressed if there are no fragments left in the fragment manager.
#Override
public void onBackPressed() {
super.onBackPressed();
FragmentManager manager = getSupportFragmentManager();
List<Fragment> fragments = manager.getFragments();
if (fragments == null || fragments.size() == 0) {
Log.d(TAG, "No more fragments: exit");
super.onBackPressed();
}
}
Is this acceptable thing to do - would it create any issues in the activity workflow? Is there a better/standard way to handle this scenario?
There is no problem to do that, but probably it would be easier if when you add the main fragment to the activity you do NOT call .addToBackStack()
You don't really need to override onBackPressed in your Activity. I would suggest implementing a method for adding fragments in your Activity:
protected void addFragment(Fragment fragment, boolean addToBackStack) {
String tag = fragment.getClass().getName(); //It's optional, may be null
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction()
.add(R.id.your_container_id, fragment, tag);
if (addToBackStack) {
transaction.addToBackStack(tag);
}
transaction.commit();
}
And modify your onCreate method of activity like in the following snippet:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
// Add your fragment only if it is a first launch,
// otherwise it will be restored by system
addFragment(new YourFirstFragment(), false);
}
}
For all other fragments use:
addFragment(new OtherFragment(), true);

Android: keep Fragment running

Is it possible to keep a Fragment running while I call replace from FragmentManager to open a new Fragment?
Basically I don't want to pause a Fragment when navigating (via replace method) to another Fragment.
Is it possible?
Or the correct approach is, always, instantiate a new Fragment every time I need to open it and restore its previous state?
Thanks!
FragmentManger replace method will destroy the previous fragment completely, So in each transaction onDestroyView(), onDestroy() and onDetach() will get called on previous fragment. If you want to keep your fragment running you can instead use FragmentManger hide() and show() methods! It hides and shows the fragments without destroying them.
so first add both fragments to fragment manager and also hide the second fragment.
fragmentManager.beginTransaction()
.add(R.id.new_card_container, FragmentA)
.add(R.id.new_card_container,FragmentB)
.hide(FragmentB)
.commit();
Note that you can only call show() on hidden fragment. So here you can't call show() on FragmentA but it's not a problem because by hiding and showing FragmentB you can get replacement effect you want.
And here is a method to go back and forth between your fragments.
public void showOtherFragment() {
if(FragmentB.isHidden()){
fragmentManager.beginTransaction()
.show(FragmentB).commit();
} else {
fragmentManager.beginTransaction()
.hide(FragmentB).commit();
}
}
Now if you put log message in fragment callback method you will see there is no destruction (except for screen orientation change!), even view will not get destroyed since onDistroyView doesn't get called.
There is only one problem and that is, first time when application starts onCreateView() method get called one time for each fragment (and it should be!) but when the orientation changes onCreateView() gets called twice for each fragment and that's because fragments once created as usual and once because of there attachment to FragmentManger (saved on bundle object) To avoid that you have two options 1) detach fragments in onSaveInstaneState() callback.
#Override
protected void onSaveInstanceState(Bundle outState) {
fragmentManager.beginTransaction()
.detach(FragmentA)
.detach(FragmentB)
.commit();
super.onSaveInstanceState(outState);
}
It's working but view state will not get updated automatically, for example if you have a EditText its text will erase each time orientation change happens. of course you can fix this simply by saving states in the fragment but you don't have to if you use the second option!
first i save a Boolean value in onSaveInstaneState() method to remember witch fragment is shown.
#Override
protected void onSaveInstanceState(Bundle outState) {
boolean isFragAVisible = true;
if(!FragmentB.isHidden())
isFragAVisible = false;
outState.putBoolean("isFragAVisible",isFragAVisible);
super.onSaveInstanceState(outState);
}
now in activity onCreate method i check to see if savedInstanceState == null. if yes do as usual if not activity is created for second time. so fragment manager already contains the fragments. So instead i'm getting a reference to my fragments from fragment manager. also i make sure correct fragment is shown since its not recovered automatically.
fragmentManager = getFragmentManager();
if(savedInstanceState == null){
FragmentA = new FragmentA();
FragmentB = new FragmentB();
fragmentManager.beginTransaction()
.add(R.id.new_card_container, FragmentA, "fragA")
.add(R.id.new_card_container, FragmentB, "fragB")
.hide(FragmentB)
.commit();
} else {
FragmentA = (FragmentA) fragmentManager.findFragmentByTag("fragA");
FragmentB = (FragmentB) fragmentManager.findFragmentByTag("fragB");
boolean isFragAVisible = savedInstanceState.getBoolean("isFragAVisible");
if(isFragAVisible)
fragmentManager.beginTransaction()
.hide(FragmentB)
.commit();
else
fragmentManager.beginTransaction()
.hide(FragmetA) //only if using transaction animation
.commit();
}
By now your fragment will work perfectly if are not using transaction animation. If you do, you also need to show and hide FragmentA. So when you want to show FragmentB first hide FragmentA then show FragmentB (in the same transaction) and when you want to hide FragmentB hide it first and also show FragmentA (again in the same transaction). Here is my code for card flip animation (downloaded from developer.goodle.com)
public void flipCard(String direction) {
int animationEnter, animationLeave;
if(direction == "left"){
animationEnter = R.animator.card_flip_right_in;
animationLeave = R.animator.card_flip_right_out;
} else {
animationEnter = R.animator.card_flip_left_in;
animationLeave = R.animator.card_flip_left_out;
}
if(cardBack.isHidden()){
fragmentManager.beginTransaction()
.setCustomAnimations(animationEnter, animationLeave)
.hide(cardFront)
.show(cardBack)
.commit();
} else {
fragmentManager.beginTransaction()
.setCustomAnimations(animationEnter,animationLeave)
.hide(cardBack)
.show(cardFront)
.commit();
}
}

listView is not updated immediately

I have an Activity, which contains 2 dynamic Fragments (Exactly one of them is displayed at a time).
one of the Fragments (HasExploitsFragment) contains a listView.
When the activity resumes (the resume function has been called), (after an object added to the list)
the list (at the HasExploitsFragment fragment) is not updated..
The resume function is:
#Override
protected void onResume() {
super.onResume();
if (exploitTitleAndDescription.get(HasGroupsFragment.groupChosen)
.size() == 0) {
// Hide the fragment
FragmentManager fm = getFragmentManager();
fm.beginTransaction()
.setCustomAnimations(android.R.animator.fade_in,
android.R.animator.fade_out)
.hide(hasExploitsFragment).commit();
addDynamicNoExploitsFragment();
} else {
//We enter this section, but despite that, the update is not shown immediately :(
FragmentManager fm = getFragmentManager();
fm.beginTransaction()
.setCustomAnimations(android.R.animator.fade_in,
android.R.animator.fade_out)
.hide(noExploitsFragment).commit();
addDynamicHasExploitsFragment();
}
}
and the addDynamicHasExploitsFragment() is the following:
private void addDynamicHasExploitsFragment() {
hasExploitsFragment = new HasExploitsFragment();
getFragmentManager().beginTransaction()
.add(R.id.exploits_main_menu, hasExploitsFragment).commit();
}
When I exit the Activity and enters again, the list is updated.
How can I perform the update immediately (At the resume function details above)?
You need to call notifyDataSetChanged() to update listview.

Handling Back Button with Fragment

In my MainActivity, I am launching a fragment using the following:
private void displayView() {
Log.d("displayView", "in select item");
// update the main content by replacing fragments
Fragment fragment = null;
fragment = new WorkoutsFragment();
if (fragment != null) {
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.add(R.id.main_container, fragment)
.addToBackStack("fragBack")
.commit();
} else {
// error in creating fragment
Log.e("MainActivity", "Error in creating fragment");
}
}
This loads my fragment correctly, and I am able to see it, however, when I hit the back button it exits the application. I would like it to go back to MainActivity if possible.
Is this improper handling of a fragment? If so, what would be the correct way of approaching this?
Thanks!
You can override onBackPressed in your main activity and not call super.onBackPressed. In the overriden method, you can remove the fragment from the fragment manager.

Fragment still visible after pressing the back button

I want to have a Fragment which provides the ability to create someting.
After that i want to show the new "someting" in another fragment. After pressing the back button on the device i want to go back to the MainFragment and not to the CreateFragment (this works well). But after that the ShowFragment is still visible.
Here is my code:
In my MainActivity i got a MainFragment which has a button "Create".
After tap the button i load a "Create" Fragment.
fragmentManager.beginTransaction()
.replace(R.id.container, CreateFragment.newInstance())
.addToBackStack("Create")
.commit();
If the user has entered some details he taps the "Ok" Button. This fires the following on the MainActivity.
fragmentManager.beginTransaction()
.replace(R.id.container, ShowFragment.newInstance(id))
.commit();
So far so good, but here comes the problem.
If the user taps the back button on the device he gets back to the MainFragment BUT the ShowFragment is still visible (under the MainFragment).
Update
This is what happens:
MainFragment > CreateFragment > ShowFragment > (BACK Button) > MainFragment (ShowFragment in the back)
Just pop the ShowFragment from the stack on the onBackPress Event as below:
#Override
public void onBackPressed() {
final Fragment fragment = fragmentManager.findFragmentById(R.id.container);
if (fragment != null) {
fragmentManager.popBackStack();
} else {
super.onBackPressed();
}
}
Press the back button of fragment ShowFragment within your ShowFragment fragment will call the onBackPressed() method. Then when you call method popBackStack(), it will return you back to the CreateFragment.
Sample code -
public void onBackPressed()
{
FragmentManager fm = getActivity().getSupportFragmentManager();
fm.popBackStack();
}
See the post how-to-back-to-previous-fragment-on-pressing-manually-back-button-of-indivisual-fragment for more info with similar situation.

Categories

Resources