Destroy fragment's presenter when they aren't needed using FragmentStack class from Nucleus' examples
This commit is contained in:
parent
11563e6f95
commit
b389db9773
6 changed files with 220 additions and 12 deletions
179
app/src/main/java/eu/kanade/mangafeed/ui/main/FragmentStack.java
Normal file
179
app/src/main/java/eu/kanade/mangafeed/ui/main/FragmentStack.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ package eu.kanade.mangafeed.ui.main;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.widget.FrameLayout;
|
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.download.DownloadFragment;
|
||||||
import eu.kanade.mangafeed.ui.library.LibraryFragment;
|
import eu.kanade.mangafeed.ui.library.LibraryFragment;
|
||||||
import eu.kanade.mangafeed.ui.setting.SettingsActivity;
|
import eu.kanade.mangafeed.ui.setting.SettingsActivity;
|
||||||
|
import nucleus.view.ViewWithPresenter;
|
||||||
|
|
||||||
public class MainActivity extends BaseActivity {
|
public class MainActivity extends BaseActivity {
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ public class MainActivity extends BaseActivity {
|
||||||
FrameLayout container;
|
FrameLayout container;
|
||||||
|
|
||||||
private Drawer drawer;
|
private Drawer drawer;
|
||||||
|
private FragmentStack fragmentStack;
|
||||||
|
|
||||||
private final static String SELECTED_ITEM = "selected_item";
|
private final static String SELECTED_ITEM = "selected_item";
|
||||||
|
|
||||||
|
@ -40,6 +41,12 @@ public class MainActivity extends BaseActivity {
|
||||||
|
|
||||||
setupToolbar(toolbar);
|
setupToolbar(toolbar);
|
||||||
|
|
||||||
|
fragmentStack = new FragmentStack(this, getSupportFragmentManager(), R.id.content_layout,
|
||||||
|
fragment -> {
|
||||||
|
if (fragment instanceof ViewWithPresenter)
|
||||||
|
((ViewWithPresenter)fragment).getPresenter().destroy();
|
||||||
|
});
|
||||||
|
|
||||||
drawer = new DrawerBuilder()
|
drawer = new DrawerBuilder()
|
||||||
.withActivity(this)
|
.withActivity(this)
|
||||||
.withRootView(container)
|
.withRootView(container)
|
||||||
|
@ -103,17 +110,7 @@ public class MainActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFragment(Fragment fragment) {
|
public void setFragment(Fragment fragment) {
|
||||||
try {
|
fragmentStack.replace(fragment);
|
||||||
if (fragment != null && getSupportFragmentManager() != null) {
|
|
||||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
|
||||||
if (ft != null) {
|
|
||||||
ft.replace(R.id.content_layout, fragment);
|
|
||||||
ft.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
8
app/src/main/res/anim/enter_from_left.xml
Normal file
8
app/src/main/res/anim/enter_from_left.xml
Normal 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>
|
8
app/src/main/res/anim/enter_from_right.xml
Normal file
8
app/src/main/res/anim/enter_from_right.xml
Normal 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>
|
8
app/src/main/res/anim/exit_to_left.xml
Normal file
8
app/src/main/res/anim/exit_to_left.xml
Normal 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>
|
8
app/src/main/res/anim/exit_to_right.xml
Normal file
8
app/src/main/res/anim/exit_to_right.xml
Normal 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>
|
Reference in a new issue