From b389db9773190123fee213a50cefcf39b988baee Mon Sep 17 00:00:00 2001 From: inorichi Date: Tue, 17 Nov 2015 00:28:22 +0100 Subject: [PATCH] Destroy fragment's presenter when they aren't needed using FragmentStack class from Nucleus' examples --- .../mangafeed/ui/main/FragmentStack.java | 179 ++++++++++++++++++ .../mangafeed/ui/main/MainActivity.java | 21 +- app/src/main/res/anim/enter_from_left.xml | 8 + app/src/main/res/anim/enter_from_right.xml | 8 + app/src/main/res/anim/exit_to_left.xml | 8 + app/src/main/res/anim/exit_to_right.xml | 8 + 6 files changed, 220 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/eu/kanade/mangafeed/ui/main/FragmentStack.java create mode 100644 app/src/main/res/anim/enter_from_left.xml create mode 100644 app/src/main/res/anim/enter_from_right.xml create mode 100644 app/src/main/res/anim/exit_to_left.xml create mode 100644 app/src/main/res/anim/exit_to_right.xml diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/main/FragmentStack.java b/app/src/main/java/eu/kanade/mangafeed/ui/main/FragmentStack.java new file mode 100644 index 0000000000..1963da68c1 --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/ui/main/FragmentStack.java @@ -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 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 a type of callback. + * @return a back fragment or activity. + */ + @SuppressWarnings("unchecked") + public T findCallback(Fragment fragment, Class 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 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 getFragments() { + List 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); + } +} diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/main/MainActivity.java b/app/src/main/java/eu/kanade/mangafeed/ui/main/MainActivity.java index 7c87a36fc7..541daf8aeb 100644 --- a/app/src/main/java/eu/kanade/mangafeed/ui/main/MainActivity.java +++ b/app/src/main/java/eu/kanade/mangafeed/ui/main/MainActivity.java @@ -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); } } diff --git a/app/src/main/res/anim/enter_from_left.xml b/app/src/main/res/anim/enter_from_left.xml new file mode 100644 index 0000000000..ee8a54edfc --- /dev/null +++ b/app/src/main/res/anim/enter_from_left.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/enter_from_right.xml b/app/src/main/res/anim/enter_from_right.xml new file mode 100644 index 0000000000..cd4423ec21 --- /dev/null +++ b/app/src/main/res/anim/enter_from_right.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/exit_to_left.xml b/app/src/main/res/anim/exit_to_left.xml new file mode 100644 index 0000000000..ba2a10497a --- /dev/null +++ b/app/src/main/res/anim/exit_to_left.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/exit_to_right.xml b/app/src/main/res/anim/exit_to_right.xml new file mode 100644 index 0000000000..c479a9f149 --- /dev/null +++ b/app/src/main/res/anim/exit_to_right.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file