Issue
Short and simple version: I am trying to achieve a backstack just like the app Instagram.
How their navigation work
In instagram they use navigation from a bottombar. Without knowing how the code looks like. They create some kind of 4 lane backstack.
So lets say i click on Home (lane 1). I then manage to click on a post -> click on a user -> click on another post -> and lastly click on a hashtag
I have then added 4 pages to the backstack for that lane.
I then do something similar to another lane (lets say the account page).
I know have 2 lanes with 4 pages in both. If i would hit back button at this point. I would just traverse back at the same pages as i opened them.
But if i instead clicked back to Home (from the bottom navigation) and clicked backbutton from there. I would traverse back from lane 1 instead of lane 2.
The big question
How can i achieve this lane like backstacking? is there a simple way im not thinking of?
What i managed to do so far
I have not managed to do a lot. I created a test project where i experiment with this type of navigation. What i managed to do so far is to create one massive backstack of all my bottombar navigation pages
My guess on how i would achieve this kind of feature is to move some parts of the backstack to the top and move the other back. But How is that possible?
Solution
I managed to fix the issue i was having. Dont give me full credit. I had a reference code that i looked at. Please check below.
This is maybe not a solid solution. But it may help you understand the logic behind how it works. So you can create your own solution that fits your needs.
Hope this helps :)
Background how my app works
To start with. I just want to let you all know how my app works. So you know why i chose my route of implementing this navigation. I have an activity that keeps the references to all the root fragments that it is using. The activity only adds one root fragment at a time. Depending on what button the user clicks.
When the activity creates a new root fragment. It will push it to the managing class that handles it.
The activity itself overrides onBackPressed()
and calls the made up backstacks onBackpressed()
function.
The activity will also pass a listener to the managing class. This listener will tell the activity when to shut down the app. And also when to refresh the current active fragment
BackStackManager.java
This is the managing class. It keeps a reference to all the different backstacks. It also delegates any fragment transactional duties to its FragmentManager class.
public class BackStackManager {
//region Members
/** Reference to the made up backstack */
private final LinkedList<BackStack> mBackStacks;
/** Reference to listener */
private final BackStackHelperListener mListener;
/** Reference to internal fragment manager */
private final BackStackFragmentManager mFragmentManager;
//endregion
//region Constructors
public BackStackManager(@NonNull final BackStackHelperListener listener,
@NonNull final FragmentManager fragmentManager) {
mBackStacks = new LinkedList<>();
mListener = listener;
mFragmentManager = new BackStackFragmentManager(fragmentManager);
}
//endregion
//region Methods
/** When adding a new root fragment
* IMPORTANT: Activity his holding the reference to the root. */
public void addRootFragment(@NonNull final Fragment fragment,
final int layoutId) {
if (!isAdded(fragment)) {
addRoot(fragment, layoutId);
}
else if (isAdded(fragment) && isCurrent(fragment)) {
refreshCurrentRoot();
}
else {
switchRoot(fragment);
mFragmentManager.switchFragment(fragment);
}
}
/** When activity is calling onBackPressed */
public void onBackPressed() {
final BackStack current = mBackStacks.peekLast();
final String uuid = current.pop();
if (uuid == null) {
removeRoot(current);
}
else {
mFragmentManager.popBackStack(uuid);
}
}
/** Adding child fragment */
public void addChildFragment(@NonNull final Fragment fragment,
final int layoutId) {
final String uuid = UUID.randomUUID().toString();
final BackStack backStack = mBackStacks.peekLast();
backStack.push(uuid);
mFragmentManager.addChildFragment(fragment, layoutId, uuid);
}
/** Remove root */
private void removeRoot(@NonNull final BackStack backStack) {
mBackStacks.remove(backStack);
//After removing. Call close app listener if the backstack is empty
if (mBackStacks.isEmpty()) {
mListener.closeApp();
}
//Change root since the old one is out
else {
BackStack newRoot = mBackStacks.peekLast();
mFragmentManager.switchFragment(newRoot.mRootFragment);
}
}
/** Adding root fragment */
private void addRoot(@NonNull final Fragment fragment, final int layoutId) {
mFragmentManager.addFragment(fragment, layoutId);
//Create a new backstack and add it to the list
final BackStack backStack = new BackStack(fragment);
mBackStacks.offerLast(backStack);
}
/** Switch root internally in the made up backstack */
private void switchRoot(@NonNull final Fragment fragment) {
for (int i = 0; i < mBackStacks.size(); i++) {
BackStack backStack = mBackStacks.get(i);
if (backStack.mRootFragment == fragment) {
mBackStacks.remove(i);
mBackStacks.offerLast(backStack);
break;
}
}
}
/** Let listener know to call refresh */
private void refreshCurrentRoot() {
mListener.refresh();
}
/** Convenience method */
private boolean isAdded(@NonNull final Fragment fragment) {
for (BackStack backStack : mBackStacks) {
if (backStack.mRootFragment == fragment) {
return true;
}
}
return false;
}
/** Convenience method */
private boolean isCurrent(@NonNull final Fragment fragment) {
final BackStack backStack = mBackStacks.peekLast();
return backStack.mRootFragment == fragment;
}
//endregion
}
BackStackFragmentManager.java
This class handles all fragment transactions. such as adding/removing/hiding/showing. This class lives within the BackStackManager class.
public class BackStackFragmentManager {
//region Members
/** Reference to fragment manager */
private final FragmentManager mFragmentManager;
/** Last added fragment */
private Fragment mLastAdded;
//endregion
//region Constructors
public BackStackFragmentManager(@NonNull final FragmentManager fragmentManager) {
mFragmentManager = fragmentManager;
}
//endregion
//region Methods
/** Switch root fragment */
public void switchFragment(@NonNull final Fragment fragment) {
final FragmentTransaction transaction = mFragmentManager.beginTransaction();
transaction.show(fragment);
transaction.hide(mLastAdded);
transaction.commit();
mLastAdded = fragment;
}
/** Adding child fragment to a root */
public void addChildFragment(@NonNull final Fragment fragment,
final int layoutId,
@NonNull final String tag) {
final FragmentTransaction transaction = mFragmentManager.beginTransaction();
transaction.add(layoutId, fragment, tag);
transaction.commit();
}
/** Add a root fragment */
public void addFragment(@NonNull Fragment fragment, int layoutId) {
final FragmentTransaction transaction = mFragmentManager.beginTransaction();
//since we hide/show. This should only happen initially
if (!fragment.isAdded()) {
transaction.add(layoutId, fragment, fragment.getClass().getName());
}
else {
transaction.show(fragment);
}
if (mLastAdded != null) {
transaction.hide(mLastAdded);
}
transaction.commit();
mLastAdded = fragment;
}
/** Pop back stack
* Function is removing childs that is not used!
*/
public void popBackStack(@NonNull final String tag) {
final Fragment fragment = mFragmentManager.findFragmentByTag(tag);
final FragmentTransaction transaction = mFragmentManager.beginTransaction();
transaction.remove(fragment);
transaction.commit();
}
//endregion
}
BackStack.java
This is a simple class that just handles internal references to the root and tags to all the backstack child entries. And also handling of these child entries
public class BackStack {
//region Members
public final Fragment mRootFragment;
final LinkedList<String> mStackItems;
//endregion
//region Constructors
public BackStack(@NonNull final Fragment rootFragment) {
mRootFragment = rootFragment;
mStackItems = new LinkedList<>();
}
//endregion
//region Methods
public String pop() {
if (isEmpty()) return null;
return mStackItems.pop();
}
public void push(@NonNull final String id) {
mStackItems.push(id);
}
public boolean isEmpty() {
return mStackItems.isEmpty();
}
//endregion
}
Listener
Not much to say about this. It is implemented by the activity
public interface BackStackHelperListener {
/** Let the listener know that the app should close. The backstack is depleted */
void closeApp();
/** Let the listener know that the user clicked on an already main root. So app can do
* a secondary action if needed
*/
void refresh();
}
References
https://blog.f22labs.com/instagram-like-bottom-tab-fragment-transaction-android-389976fb8759
Answered By - Jemil Riahi
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.