Destroy fragment's presenter when they aren't needed using FragmentStack class from Nucleus' examples

This commit is contained in:
inorichi 2015-11-17 00:28:22 +01:00
parent 11563e6f95
commit b389db9773
6 changed files with 220 additions and 12 deletions

View File

@ -0,0 +1,179 @@
package eu.kanade.mangafeed.ui.main;
import android.app.Activity;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import java.util.ArrayList;
import java.util.List;
import eu.kanade.mangafeed.R;
/**
* Why this class is needed.
*
* FragmentManager does not supply a developer with a fragment stack.
* It gives us a fragment *transaction* stack.
*
* To be sane, we need *fragment* stack.
*
* This implementation also handles NucleusSupportFragment presenter`s lifecycle correctly.
*/
public class FragmentStack {
public interface OnBackPressedHandlingFragment {
boolean onBackPressed();
}
public interface OnFragmentRemovedListener {
void onFragmentRemoved(Fragment fragment);
}
private Activity activity;
private FragmentManager manager;
private int containerId;
@Nullable private OnFragmentRemovedListener onFragmentRemovedListener;
public FragmentStack(Activity activity, FragmentManager manager, int containerId, @Nullable OnFragmentRemovedListener onFragmentRemovedListener) {
this.activity = activity;
this.manager = manager;
this.containerId = containerId;
this.onFragmentRemovedListener = onFragmentRemovedListener;
}
/**
* Returns the number of fragments in the stack.
*
* @return the number of fragments in the stack.
*/
public int size() {
return getFragments().size();
}
/**
* Pushes a fragment to the top of the stack.
*/
public void push(Fragment fragment) {
Fragment top = peek();
if (top != null) {
manager.beginTransaction()
.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right)
.remove(top)
.add(containerId, fragment, indexToTag(manager.getBackStackEntryCount() + 1))
.addToBackStack(null)
.commit();
}
else {
manager.beginTransaction()
.add(containerId, fragment, indexToTag(0))
.commit();
}
manager.executePendingTransactions();
}
/**
* Pops the top item if the stack.
* If the fragment implements {@link OnBackPressedHandlingFragment}, calls {@link OnBackPressedHandlingFragment#onBackPressed()} instead.
* If {@link OnBackPressedHandlingFragment#onBackPressed()} returns false the fragment gets popped.
*
* @return true if a fragment has been popped or if {@link OnBackPressedHandlingFragment#onBackPressed()} returned true;
*/
public boolean back() {
Fragment top = peek();
if (top instanceof OnBackPressedHandlingFragment) {
if (((OnBackPressedHandlingFragment)top).onBackPressed())
return true;
}
return pop();
}
/**
* Pops the topmost fragment from the stack.
* The lowest fragment can't be popped, it can only be replaced.
*
* @return false if the stack can't pop or true if a top fragment has been popped.
*/
public boolean pop() {
if (manager.getBackStackEntryCount() == 0)
return false;
Fragment top = peek();
manager.popBackStackImmediate();
if (onFragmentRemovedListener != null)
onFragmentRemovedListener.onFragmentRemoved(top);
return true;
}
/**
* Replaces stack contents with just one fragment.
*/
public void replace(Fragment fragment) {
List<Fragment> fragments = getFragments();
manager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
manager.beginTransaction()
.replace(containerId, fragment, indexToTag(0))
.commit();
manager.executePendingTransactions();
if (onFragmentRemovedListener != null) {
for (Fragment fragment1 : fragments)
onFragmentRemovedListener.onFragmentRemoved(fragment1);
}
}
/**
* Returns the topmost fragment in the stack.
*/
public Fragment peek() {
return manager.findFragmentById(containerId);
}
/**
* Returns a back fragment if the fragment is of given class.
* If such fragment does not exist and activity implements the given class then the activity will be returned.
*
* @param fragment a fragment to search from.
* @param callbackType a class of type for callback to search.
* @param <T> a type of callback.
* @return a back fragment or activity.
*/
@SuppressWarnings("unchecked")
public <T> T findCallback(Fragment fragment, Class<T> callbackType) {
Fragment back = getBackFragment(fragment);
if (back != null && callbackType.isAssignableFrom(back.getClass()))
return (T)back;
if (callbackType.isAssignableFrom(activity.getClass()))
return (T)activity;
return null;
}
private Fragment getBackFragment(Fragment fragment) {
List<Fragment> fragments = getFragments();
for (int f = fragments.size() - 1; f >= 0; f--) {
if (fragments.get(f) == fragment && f > 0)
return fragments.get(f - 1);
}
return null;
}
private List<Fragment> getFragments() {
List<Fragment> fragments = new ArrayList<>(manager.getBackStackEntryCount() + 1);
for (int i = 0; i < manager.getBackStackEntryCount() + 1; i++) {
Fragment fragment = manager.findFragmentByTag(indexToTag(i));
if (fragment != null)
fragments.add(fragment);
}
return fragments;
}
private String indexToTag(int index) {
return Integer.toString(index);
}
}

View File

@ -3,7 +3,6 @@ package eu.kanade.mangafeed.ui.main;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.widget.Toolbar;
import android.widget.FrameLayout;
@ -19,6 +18,7 @@ import eu.kanade.mangafeed.ui.catalogue.SourceFragment;
import eu.kanade.mangafeed.ui.download.DownloadFragment;
import eu.kanade.mangafeed.ui.library.LibraryFragment;
import eu.kanade.mangafeed.ui.setting.SettingsActivity;
import nucleus.view.ViewWithPresenter;
public class MainActivity extends BaseActivity {
@ -29,6 +29,7 @@ public class MainActivity extends BaseActivity {
FrameLayout container;
private Drawer drawer;
private FragmentStack fragmentStack;
private final static String SELECTED_ITEM = "selected_item";
@ -40,6 +41,12 @@ public class MainActivity extends BaseActivity {
setupToolbar(toolbar);
fragmentStack = new FragmentStack(this, getSupportFragmentManager(), R.id.content_layout,
fragment -> {
if (fragment instanceof ViewWithPresenter)
((ViewWithPresenter)fragment).getPresenter().destroy();
});
drawer = new DrawerBuilder()
.withActivity(this)
.withRootView(container)
@ -103,17 +110,7 @@ public class MainActivity extends BaseActivity {
}
public void setFragment(Fragment fragment) {
try {
if (fragment != null && getSupportFragmentManager() != null) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (ft != null) {
ft.replace(R.id.content_layout, fragment);
ft.commit();
}
}
} catch (Exception e) {
}
fragmentStack.replace(fragment);
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="400"
android:fromXDelta="-100%"
android:toXDelta="0%" />
</set>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="400"
android:fromXDelta="100%"
android:toXDelta="0%" />
</set>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="400"
android:fromXDelta="0%"
android:toXDelta="-100%" />
</set>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="400"
android:fromXDelta="0%"
android:toXDelta="100%" />
</set>