Navigation refactor, DelegationListAdapter fix

This commit is contained in:
Denis Karmyshakov 2018-03-19 12:29:25 +03:00
parent 69f1c9af4a
commit 5f6152050b
7 changed files with 318 additions and 508 deletions

View File

@ -18,7 +18,7 @@ class DelegatesManager {
return delegate.itemViewType
}
}
throw IllegalStateException("Delegate not found")
throw IllegalStateException("Delegate not found for adapterPosition: $adapterPosition")
}
fun getItemId(items: List<*>, adapterPosition: Int, collectionPosition: Int): Long {
@ -33,19 +33,12 @@ class DelegatesManager {
delegate.onBindViewHolder(holder, items, adapterPosition, collectionPosition, payloads)
}
/**
* Adds [ItemAdapterDelegate] to adapter.
*
* @param delegate Delegate to add.
*/
fun addDelegate(delegate: ItemAdapterDelegate<*, *>) = delegates.put(delegate.itemViewType, delegate)
/**
* Adds [PositionAdapterDelegate] to adapter.
*
* @param delegate Delegate to add.
*/
fun addDelegate(delegate: PositionAdapterDelegate<*>) = delegates.put(delegate.itemViewType, delegate)
fun addDelegate(delegate: AdapterDelegate<*>) = delegates.put(delegate.itemViewType, delegate)
/**
* Removes [AdapterDelegate] from adapter.

View File

@ -41,18 +41,18 @@ open class DelegationListAdapter<TItem>(diffCallback: DiffUtil.ItemCallback<TIte
final override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) = Unit
/**
* Adds [ItemAdapterDelegate] to adapter.
* Adds [AdapterDelegate] to adapter.
*
* @param delegate Delegate to add.
*/
fun addDelegate(delegate: ItemAdapterDelegate<*, *>) = delegatesManager.addDelegate(delegate)
fun addDelegate(delegate: AdapterDelegate<*>) = delegatesManager.addDelegate(delegate)
/**
* Adds [PositionAdapterDelegate] to adapter.
* Removes [AdapterDelegate] from adapter.
*
* @param delegate Delegate to add.
* @param delegate Delegate to remove.
*/
fun addDelegate(delegate: PositionAdapterDelegate<*>) = delegatesManager.addDelegate(delegate)
fun removeDelegate(delegate: AdapterDelegate<*>) = delegatesManager.removeDelegate(delegate)
/**
* Submits a new list to be diffed, and displayed.

View File

@ -1,433 +0,0 @@
/*
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
*
* This file is part of RoboSwag library.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package ru.touchin.roboswag.components.navigation;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.MenuItem;
import io.reactivex.functions.Function;
import ru.touchin.roboswag.core.log.Lc;
/**
* Created by Gavriil Sitnikov on 07/03/2016.
* Navigation which is controlling fragments on activity using {@link android.support.v4.app.FragmentManager}.
* Basically there are 4 main actions to add fragments to activity.
* 1) {@link #setInitial} means to set fragment on top and remove all previously added fragments from stack;
* 2) {@link #push} means to simply add fragment on top of the stack;
* 3) {@link #setAsTop} means to push fragment on top of the stack with specific {@link #TOP_FRAGMENT_TAG_MARK} tag.
* It is useful to realize up/back navigation: if {@link #up()} method will be called then stack will go to nearest fragment with TOP tag.
* If {@link #back()} method will be called then stack will go to previous fragment.
* Usually such logic using to set as top fragments from sidebar and show hamburger when some of them appeared;
* 4) {@link #pushForResult} means to push fragment with target fragment. It is also adding {@link #WITH_TARGET_FRAGMENT_TAG_MARK} tag.
* Also if such up/back navigation logic is not OK then {@link #backTo(Function)} method could be used with any condition to back to.
* In that case in any stack-change method it is allowed to setup fragment transactions.
*/
public class FragmentNavigation {
protected static final String TOP_FRAGMENT_TAG_MARK = "TOP_FRAGMENT";
protected static final String WITH_TARGET_FRAGMENT_TAG_MARK = "FRAGMENT_WITH_TARGET";
@NonNull
private final Context context;
@NonNull
private final FragmentManager fragmentManager;
@IdRes
private final int containerViewId;
public FragmentNavigation(@NonNull final Context context, @NonNull final FragmentManager fragmentManager, @IdRes final int containerViewId) {
this.context = context;
this.fragmentManager = fragmentManager;
this.containerViewId = containerViewId;
}
/**
* Returns {@link Context} that is using to instantiate fragments.
*
* @return {@link Context}.
*/
@NonNull
public Context getContext() {
return context;
}
/**
* Returns {@link FragmentManager} using for navigation.
*
* @return {@link FragmentManager}.
*/
@NonNull
public FragmentManager getFragmentManager() {
return fragmentManager;
}
/**
* Returns if last fragment in stack is top (added by {@link #setAsTop} or {@link #setInitial}) like fragment from sidebar menu.
*
* @return True if last fragment on stack has TOP_FRAGMENT_TAG_MARK.
*/
public boolean isCurrentFragmentTop() {
if (fragmentManager.getBackStackEntryCount() == 0) {
return true;
}
final String topFragmentTag = fragmentManager
.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() - 1)
.getName();
return topFragmentTag != null && topFragmentTag.contains(TOP_FRAGMENT_TAG_MARK);
}
/**
* Allowed to react on {@link android.app.Activity}'s menu item selection.
*
* @param item Selected menu item;
* @return True if reaction fired.
*/
@SuppressLint("InlinedApi")
//InlinedApi: it is ok as android.R.id.home contains in latest SDK
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
return item.getItemId() == android.R.id.home && back();
}
/**
* Base method which is adding fragment to stack.
*
* @param fragmentClass Class of {@link Fragment} to instantiate;
* @param targetFragment Target fragment to be set as {@link Fragment#getTargetFragment()} of instantiated {@link Fragment};
* @param addToStack Flag to add this transaction to the back stack;
* @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment};
* @param backStackTag Tag of {@link Fragment} in back stack;
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
@SuppressLint("CommitTransaction")
//CommitTransaction: it is ok as we could setup transaction before commit
protected void addToStack(@NonNull final Class<? extends Fragment> fragmentClass,
@Nullable final Fragment targetFragment,
final boolean addToStack,
@Nullable final Bundle args,
@Nullable final String backStackTag,
@Nullable final Function<FragmentTransaction, FragmentTransaction> transactionSetup) {
if (fragmentManager.isDestroyed()) {
Lc.assertion("FragmentManager is destroyed");
return;
}
final Fragment fragment = Fragment.instantiate(context, fragmentClass.getName(), args);
if (targetFragment != null) {
if (fragmentManager != targetFragment.getFragmentManager()) {
Lc.assertion("FragmentManager of target is differ then of creating fragment. Target will be lost after restoring activity. "
+ targetFragment.getFragmentManager() + " != " + fragmentManager);
}
fragment.setTargetFragment(targetFragment, 0);
}
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
.replace(containerViewId, fragment, null);
if (addToStack) {
fragmentTransaction.addToBackStack(backStackTag);
}
if (fragmentManager.getBackStackEntryCount() != 0) {
fragmentTransaction.setTransition(getDefaultTransition());
}
if (transactionSetup != null) {
try {
transactionSetup.apply(fragmentTransaction).commit();
} catch (final Exception exception) {
Lc.assertion(exception);
fragmentTransaction.commit();
}
} else {
fragmentTransaction.commit();
}
}
/**
* Returns default transition animation.
*
* @return {@link FragmentTransaction#TRANSIT_FRAGMENT_OPEN}.
*/
protected int getDefaultTransition() {
return FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
}
/**
* Simply calls {@link FragmentManager#popBackStack()}.
*
* @return True if it have back to some entry in stack.
*/
public boolean back() {
if (fragmentManager.getBackStackEntryCount() > 1) {
fragmentManager.popBackStack();
return true;
}
return false;
}
/**
* Backs to fragment which back stack's entry satisfy to specific condition.
*
* @param condition Condition of back stack entry to be satisfied;
* @return True if it have back to some entry in stack.
*/
public boolean backTo(@NonNull final Function<FragmentManager.BackStackEntry, Boolean> condition) {
final int stackSize = fragmentManager.getBackStackEntryCount();
Integer id = null;
try {
for (int i = stackSize - 2; i >= 0; i--) {
final FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i);
id = backStackEntry.getId();
if (condition.apply(backStackEntry)) {
break;
}
}
} catch (final Exception exception) {
Lc.assertion(exception);
return false;
}
if (id != null) {
fragmentManager.popBackStack(id, 0);
return true;
}
return false;
}
/**
* Backs to fragment with specific {@link #TOP_FRAGMENT_TAG_MARK} tag.
* This tag is adding if fragment added to stack via {@link #setInitial} or {@link #setAsTop(Class)} methods.
* It can be used to create simple up/back navigation.
*
* @return True if it have back to some entry in stack.
*/
@SuppressWarnings("PMD.ShortMethodName")
//ShortMethodName: it is ok because method name is good!
public boolean up() {
return backTo(backStackEntry ->
backStackEntry.getName() != null && backStackEntry.getName().endsWith(TOP_FRAGMENT_TAG_MARK));
}
/**
* Pushes {@link Fragment} on top of stack.
*
* @param fragmentClass Class of {@link Fragment} to instantiate.
*/
public void push(@NonNull final Class<? extends Fragment> fragmentClass) {
addToStack(fragmentClass, null, true, null, null, null);
}
/**
* Pushes {@link Fragment} on top of stack with specific arguments.
*
* @param fragmentClass Class of {@link Fragment} to instantiate;
* @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment}.
*/
public void push(@NonNull final Class<? extends Fragment> fragmentClass,
@NonNull final Bundle args) {
addToStack(fragmentClass, null, true, args, null, null);
}
/**
* Pushes {@link Fragment} on top of stack with specific transaction setup.
*
* @param fragmentClass Class of {@link Fragment} to instantiate;
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
public void push(@NonNull final Class<? extends Fragment> fragmentClass,
@NonNull final Function<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(fragmentClass, null, true, null, null, transactionSetup);
}
/**
* Pushes {@link Fragment} on top of stack with specific arguments and transaction setup.
*
* @param fragmentClass Class of {@link Fragment} to instantiate;
* @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment};
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
public void push(@NonNull final Class<? extends Fragment> fragmentClass,
@Nullable final Bundle args,
@Nullable final Function<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(fragmentClass, null, true, args, null, transactionSetup);
}
/**
* Pushes {@link Fragment} on top of stack with specific target fragment.
*
* @param fragmentClass Class of {@link Fragment} to instantiate;
* @param targetFragment Target fragment to be set as {@link Fragment#getTargetFragment()} of instantiated {@link Fragment}.
*/
public void pushForResult(@NonNull final Class<? extends Fragment> fragmentClass,
@NonNull final Fragment targetFragment) {
addToStack(fragmentClass, targetFragment, true, null, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, null);
}
/**
* Pushes {@link Fragment} on top of stack with specific target fragment and arguments.
*
* @param fragmentClass Class of {@link Fragment} to instantiate;
* @param targetFragment Target fragment to be set as {@link Fragment#getTargetFragment()} of instantiated {@link Fragment};
* @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment}.
*/
public void pushForResult(@NonNull final Class<? extends Fragment> fragmentClass,
@NonNull final Fragment targetFragment,
@NonNull final Bundle args) {
addToStack(fragmentClass, targetFragment, true, args, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, null);
}
/**
* Pushes {@link Fragment} on top of stack with specific target fragment and transaction setup.
*
* @param fragmentClass Class of {@link Fragment} to instantiate;
* @param targetFragment Target fragment to be set as {@link Fragment#getTargetFragment()} of instantiated {@link Fragment};
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
public void pushForResult(@NonNull final Class<? extends Fragment> fragmentClass,
@NonNull final Fragment targetFragment,
@NonNull final Function<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(fragmentClass, targetFragment, true, null, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup);
}
/**
* Pushes {@link Fragment} on top of stack with specific target fragment, arguments and transaction setup.
*
* @param fragmentClass Class of {@link Fragment} to instantiate;
* @param targetFragment Target fragment to be set as {@link Fragment#getTargetFragment()} of instantiated {@link Fragment};
* @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment};
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
public void pushForResult(@NonNull final Class<? extends Fragment> fragmentClass,
@NonNull final Fragment targetFragment,
@Nullable final Bundle args,
@Nullable final Function<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(fragmentClass, targetFragment, true, args, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup);
}
/**
* Pushes {@link Fragment} on top of stack with {@link #TOP_FRAGMENT_TAG_MARK} tag used for simple up/back navigation.
*
* @param fragmentClass Class of {@link Fragment} to instantiate.
*/
public void setAsTop(@NonNull final Class<? extends Fragment> fragmentClass) {
addToStack(fragmentClass, null, true, null, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, null);
}
/**
* Pushes {@link Fragment} on top of stack with specific arguments and with {@link #TOP_FRAGMENT_TAG_MARK} tag used for simple up/back navigation.
*
* @param fragmentClass Class of {@link Fragment} to instantiate;
* @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment}.
*/
public void setAsTop(@NonNull final Class<? extends Fragment> fragmentClass,
@NonNull final Bundle args) {
addToStack(fragmentClass, null, true, args, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, null);
}
/**
* Pushes {@link Fragment} on top of stack with specific transaction setup
* and with {@link #TOP_FRAGMENT_TAG_MARK} tag used for simple up/back navigation.
*
* @param fragmentClass Class of {@link Fragment} to instantiate;
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
public void setAsTop(@NonNull final Class<? extends Fragment> fragmentClass,
@NonNull final Function<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(fragmentClass, null, true, null, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, transactionSetup);
}
/**
* Pushes {@link Fragment} on top of stack with specific transaction setup, arguments
* and with {@link #TOP_FRAGMENT_TAG_MARK} tag used for simple up/back navigation.
*
* @param fragmentClass Class of {@link Fragment} to instantiate;
* @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment};
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
public void setAsTop(@NonNull final Class<? extends Fragment> fragmentClass,
@Nullable final Bundle args,
@Nullable final Function<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(fragmentClass, null, true, args, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, transactionSetup);
}
/**
* Pops all {@link Fragment}s and places new initial {@link Fragment} on top of stack.
*
* @param fragmentClass Class of {@link Fragment} to instantiate.
*/
public void setInitial(@NonNull final Class<? extends Fragment> fragmentClass) {
setInitial(fragmentClass, null, null);
}
/**
* Pops all {@link Fragment}s and places new initial {@link Fragment} on top of stack with specific arguments.
*
* @param fragmentClass Class of {@link Fragment} to instantiate;
* @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment}.
*/
public void setInitial(@NonNull final Class<? extends Fragment> fragmentClass,
@NonNull final Bundle args) {
setInitial(fragmentClass, args, null);
}
/**
* Pops all {@link Fragment}s and places new initial {@link Fragment} on top of stack with specific transaction setup.
*
* @param fragmentClass Class of {@link Fragment} to instantiate;
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
public void setInitial(@NonNull final Class<? extends Fragment> fragmentClass,
@NonNull final Function<FragmentTransaction, FragmentTransaction> transactionSetup) {
setInitial(fragmentClass, null, transactionSetup);
}
/**
* Pops all {@link Fragment}s and places new initial {@link Fragment} on top of stack with specific transaction setup and arguments.
*
* @param fragmentClass Class of {@link Fragment} to instantiate;
* @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment};
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
public void setInitial(@NonNull final Class<? extends Fragment> fragmentClass,
@Nullable final Bundle args,
@Nullable final Function<FragmentTransaction, FragmentTransaction> transactionSetup) {
beforeSetInitialActions();
setAsTop(fragmentClass, args, transactionSetup);
}
/**
* Method calls every time before initial {@link Fragment} will be placed.
*/
protected void beforeSetInitialActions() {
if (fragmentManager.isDestroyed()) {
Lc.assertion("FragmentManager is destroyed");
return;
}
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
}

View File

@ -0,0 +1,261 @@
/*
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
*
* This file is part of RoboSwag library.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package ru.touchin.roboswag.components.navigation
import android.content.Context
import android.os.Bundle
import android.support.annotation.IdRes
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentTransaction
import android.view.MenuItem
import ru.touchin.roboswag.core.log.Lc
/**
* Created by Gavriil Sitnikov on 07/03/2016.
* Navigation which is controlling fragments on activity using [android.support.v4.app.FragmentManager].
* Basically there are 4 main actions to add fragments to activity.
* 1) [.setInitial] means to set fragment on top and remove all previously added fragments from stack;
* 2) [.push] means to simply add fragment on top of the stack;
* 3) [.setAsTop] means to push fragment on top of the stack with specific [.TOP_FRAGMENT_TAG_MARK] tag.
* It is useful to realize up/back navigation: if [.up] method will be called then stack will go to nearest fragment with TOP tag.
* If [.back] method will be called then stack will go to previous fragment.
* Usually such logic using to set as top fragments from sidebar and show hamburger when some of them appeared;
* 4) [.pushForResult] means to push fragment with target fragment. It is also adding [.WITH_TARGET_FRAGMENT_TAG_MARK] tag.
* Also if such up/back navigation logic is not OK then [.backTo] method could be used with any condition to back to.
* In that case in any stack-change method it is allowed to setup fragment transactions.
*/
open class FragmentNavigation(
private val context: Context,
private val fragmentManager: FragmentManager,
@IdRes private val containerViewId: Int,
private val transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_OPEN
) {
companion object {
const val TOP_FRAGMENT_TAG_MARK = "TOP_FRAGMENT"
const val WITH_TARGET_FRAGMENT_TAG_MARK = "FRAGMENT_WITH_TARGET"
}
/**
* Returns if last fragment in stack is top (added by [.setAsTop] or [.setInitial]) like fragment from sidebar menu.
*
* @return True if last fragment on stack has TOP_FRAGMENT_TAG_MARK.
*/
fun isCurrentFragmentTop(): Boolean {
if (fragmentManager.backStackEntryCount == 0) {
return true
}
val topFragmentTag = fragmentManager
.getBackStackEntryAt(fragmentManager.backStackEntryCount - 1)
.name
return topFragmentTag != null && topFragmentTag.contains(TOP_FRAGMENT_TAG_MARK)
}
/**
* Allowed to react on [android.app.Activity]'s menu item selection.
*
* @param item Selected menu item;
* @return True if reaction fired.
*/
fun onOptionsItemSelected(item: MenuItem): Boolean = item.itemId == android.R.id.home && back()
/**
* Base method which is adding fragment to stack.
*
* @param fragmentClass Class of [Fragment] to instantiate;
* @param targetFragment Target fragment to be set as [Fragment.getTargetFragment] of instantiated [Fragment];
* @param addToStack Flag to add this transaction to the back stack;
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param backStackTag Tag of [Fragment] in back stack;
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
fun addToStack(
fragmentClass: Class<out Fragment>,
targetFragment: Fragment?,
targetRequestCode: Int,
addToStack: Boolean,
args: Bundle?,
backStackTag: String?,
transactionSetup: ((FragmentTransaction) -> Unit)?
) {
if (fragmentManager.isDestroyed) {
Lc.assertion("FragmentManager is destroyed")
return
}
val fragment = Fragment.instantiate(context, fragmentClass.name, args)
if (targetFragment != null) {
if (fragmentManager !== targetFragment.fragmentManager) {
Lc.assertion("FragmentManager of target is differ then of creating fragment. Target will be lost after restoring activity. "
+ targetFragment.fragmentManager + " != " + fragmentManager)
}
fragment.setTargetFragment(targetFragment, targetRequestCode)
}
val fragmentTransaction = fragmentManager.beginTransaction()
.replace(containerViewId, fragment, null)
if (addToStack) {
fragmentTransaction.addToBackStack(backStackTag)
}
if (fragmentManager.backStackEntryCount != 0) {
fragmentTransaction.setTransition(transition)
}
transactionSetup?.invoke(fragmentTransaction)
fragmentTransaction.commit()
}
/**
* Simply calls [FragmentManager.popBackStack].
*
* @return True if it have back to some entry in stack.
*/
fun back(): Boolean {
if (fragmentManager.backStackEntryCount > 1) {
fragmentManager.popBackStack()
return true
}
return false
}
/**
* Backs to fragment which back stack's entry satisfy to specific condition.
*
* @param condition Condition of back stack entry to be satisfied;
* @return True if it have back to some entry in stack.
*/
fun backTo(condition: (FragmentManager.BackStackEntry) -> Boolean): Boolean {
val stackSize = fragmentManager.backStackEntryCount
var id: Int? = null
for (i in stackSize - 2 downTo 0) {
val backStackEntry = fragmentManager.getBackStackEntryAt(i)
if (condition(backStackEntry)) {
id = backStackEntry.id
break
}
}
if (id != null) {
fragmentManager.popBackStack(id, 0)
return true
}
return false
}
/**
* Backs to fragment with specific [.TOP_FRAGMENT_TAG_MARK] tag.
* This tag is adding if fragment added to stack via [.setInitial] or [.setAsTop] methods.
* It can be used to create simple up/back navigation.
*
* @return True if it have back to some entry in stack.
*/
//ShortMethodName: it is ok because method name is good!
fun up(): Boolean = backTo { backStackEntry -> backStackEntry.name != null && backStackEntry.name.endsWith(TOP_FRAGMENT_TAG_MARK) }
/**
* Pushes [Fragment] on top of stack with specific arguments and transaction setup.
*
* @param fragmentClass Class of [Fragment] to instantiate;
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
fun push(
fragmentClass: Class<out Fragment>,
args: Bundle? = null,
addToStack: Boolean = true,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(fragmentClass, null, 0, addToStack, args, null, transactionSetup)
}
/**
* Pushes [Fragment] on top of stack with specific target fragment, arguments and transaction setup.
*
* @param fragmentClass Class of [Fragment] to instantiate;
* @param targetFragment Target fragment to be set as [Fragment.getTargetFragment] of instantiated [Fragment];
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
fun pushForResult(
fragmentClass: Class<out Fragment>,
targetFragment: Fragment,
targetRequestCode: Int,
args: Bundle? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(
fragmentClass,
targetFragment,
targetRequestCode,
true,
args,
fragmentClass.name + ';'.toString() + WITH_TARGET_FRAGMENT_TAG_MARK,
transactionSetup
)
}
/**
* Pushes [Fragment] on top of stack with specific transaction setup, arguments
* and with [.TOP_FRAGMENT_TAG_MARK] tag used for simple up/back navigation.
*
* @param fragmentClass Class of [Fragment] to instantiate;
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
fun setAsTop(
fragmentClass: Class<out Fragment>,
args: Bundle? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(fragmentClass, null, 0, true, args, fragmentClass.name + ';'.toString() + TOP_FRAGMENT_TAG_MARK, transactionSetup)
}
/**
* Pops all [Fragment]s and places new initial [Fragment] on top of stack with specific transaction setup and arguments.
*
* @param fragmentClass Class of [Fragment] to instantiate;
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
@JvmOverloads
fun setInitial(
fragmentClass: Class<out Fragment>,
args: Bundle? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
beforeSetInitialActions()
setAsTop(fragmentClass, args, transactionSetup)
}
/**
* Method calls every time before initial [Fragment] will be placed.
*/
protected fun beforeSetInitialActions() {
if (fragmentManager.isDestroyed) {
Lc.assertion("FragmentManager is destroyed")
return
}
if (fragmentManager.backStackEntryCount > 0) {
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
}
}

View File

@ -107,6 +107,8 @@ public class ViewControllerFragment<TState extends Parcelable, TActivity extends
private ViewController viewController;
private Class<ViewController<TActivity, ViewControllerFragment<TState, TActivity>, TState>> viewControllerClass;
private TState state;
@Nullable
private ActivityResult pendingActivityResult;
/**
* Returns specific {@link Parcelable} which contains state of fragment and it's {@link ViewController}.
@ -192,6 +194,10 @@ public class ViewControllerFragment<TState extends Parcelable, TActivity extends
//noinspection ConstantConditions
viewController = createViewController(requireActivity(), (PlaceholderView) getView(), savedInstanceState);
viewController.onCreate();
if (pendingActivityResult != null) {
viewController.onActivityResult(pendingActivityResult.requestCode, pendingActivityResult.resultCode, pendingActivityResult.data);
pendingActivityResult = null;
}
requireActivity().invalidateOptionsMenu();
}
@ -286,8 +292,9 @@ public class ViewControllerFragment<TState extends Parcelable, TActivity extends
public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
if (viewController != null) {
viewController.onActivityResult(requestCode, resultCode, data);
} else {
pendingActivityResult = new ActivityResult(requestCode, resultCode, data);
}
super.onActivityResult(requestCode, resultCode, data);
}
private static class PlaceholderView extends FrameLayout {
@ -323,4 +330,17 @@ public class ViewControllerFragment<TState extends Parcelable, TActivity extends
}
private static class ActivityResult {
final int requestCode;
final int resultCode;
@Nullable
final Intent data;
ActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
this.requestCode = requestCode;
this.resultCode = resultCode;
this.data = data;
}
}
}

View File

@ -19,6 +19,7 @@
package ru.touchin.roboswag.components.navigation.fragments;
import android.arch.lifecycle.Lifecycle;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
@ -42,7 +43,6 @@ import ru.touchin.roboswag.core.log.Lc;
public abstract class ViewFragment<TActivity extends FragmentActivity> extends Fragment implements OnFragmentStartedListener {
private boolean appeared;
private boolean started;
/**
* Returns if fragment have parent fragment.
@ -111,7 +111,6 @@ public abstract class ViewFragment<TActivity extends FragmentActivity> extends F
@Override
public void onStart() {
super.onStart();
started = true;
callMethodAfterInstantiation(this::onStart);
}
@ -168,6 +167,7 @@ public abstract class ViewFragment<TActivity extends FragmentActivity> extends F
public void setMenuVisibility(final boolean menuVisible) {
super.setMenuVisibility(menuVisible);
if (getBaseActivity() != null && getView() != null) {
final boolean started = getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED);
if (!appeared && menuVisible && started) {
onAppear(getView(), getBaseActivity());
}
@ -209,7 +209,6 @@ public abstract class ViewFragment<TActivity extends FragmentActivity> extends F
@Deprecated
@Override
public void onStop() {
started = false;
callMethodAfterInstantiation(this::onStop);
super.onStop();
}

View File

@ -27,7 +27,6 @@ import android.support.v4.app.FragmentActivity
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentTransaction
import io.reactivex.functions.Function
import ru.touchin.roboswag.components.navigation.FragmentNavigation
import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment
@ -41,38 +40,34 @@ import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragmen
open class ViewControllerNavigation<TActivity : FragmentActivity>(
context: Context,
fragmentManager: FragmentManager,
@IdRes containerViewId: Int
) : FragmentNavigation(context, fragmentManager, containerViewId) {
@IdRes containerViewId: Int,
transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_OPEN
) : FragmentNavigation(context, fragmentManager, containerViewId, transition) {
/**
* Pushes [ViewController] on top of stack with specific [ViewControllerFragment.getState] and with specific transaction setup.
*
* @param viewControllerClass Class of [ViewController] to be pushed;
* @param state [Parcelable] of [ViewController]'s fragment;
* @param addToStack Flag to add this transaction to the back stack;
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;
* @param TState Type of state of fragment.
*/
fun <TState : Parcelable> pushViewController(
viewControllerClass: Class<out ViewController<TActivity, ViewControllerFragment<TState, TActivity>, TState>>,
state: TState,
transactionSetup: Function<FragmentTransaction, FragmentTransaction>? = null
addToStack: Boolean = true,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addViewControllerToStack(viewControllerClass, null, true, state, null, transactionSetup)
}
/**
* Pushes [ViewController] without adding to stack and with specific [ViewControllerFragment.getState].
*
* @param viewControllerClass Class of [ViewController] to be pushed;
* @param state [Parcelable] of [ViewController]'s fragment;
* @param TState Type of state of fragment.
*/
fun <TState : Parcelable> pushSingleViewController(
viewControllerClass: Class<out ViewController<TActivity, ViewControllerFragment<TState, TActivity>, TState>>,
state: TState,
transactionSetup: Function<FragmentTransaction, FragmentTransaction>? = null
) {
addViewControllerToStack(viewControllerClass, null, false, state, null, transactionSetup)
addToStack(
ViewControllerFragment::class.java,
null,
0,
addToStack,
ViewControllerFragment.args(viewControllerClass, state),
null,
transactionSetup
)
}
/**
@ -90,13 +85,15 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
viewControllerClass: Class<out ViewController<TActivity, ViewControllerFragment<TState, TActivity>, TState>>,
state: TState,
targetFragment: TTargetFragment,
transactionSetup: Function<FragmentTransaction, FragmentTransaction>? = null
targetRequestCode: Int,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addViewControllerToStack(
viewControllerClass,
addToStack(
ViewControllerFragment::class.java,
targetFragment,
targetRequestCode,
true,
state,
ViewControllerFragment.args(viewControllerClass, state),
viewControllerClass.name + ';'.toString() + WITH_TARGET_FRAGMENT_TAG_MARK,
transactionSetup
)
@ -114,13 +111,14 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
fun <TState : Parcelable> setViewControllerAsTop(
viewControllerClass: Class<out ViewController<TActivity, ViewControllerFragment<TState, TActivity>, TState>>,
state: TState,
transactionSetup: Function<FragmentTransaction, FragmentTransaction>? = null
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addViewControllerToStack(
viewControllerClass,
addToStack(
ViewControllerFragment::class.java,
null,
0,
true,
state,
ViewControllerFragment.args(viewControllerClass, state),
viewControllerClass.name + ';'.toString() + TOP_FRAGMENT_TAG_MARK,
transactionSetup
)
@ -138,38 +136,10 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
fun <TState : Parcelable> setInitialViewController(
viewControllerClass: Class<out ViewController<TActivity, ViewControllerFragment<TState, TActivity>, TState>>,
state: TState,
transactionSetup: Function<FragmentTransaction, FragmentTransaction>? = null
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
beforeSetInitialActions()
setViewControllerAsTop(viewControllerClass, state, transactionSetup)
}
/**
* Base method to push stateful [ViewControllerFragment] to stack.
*
* @param viewControllerClass Class of [ViewController] to be pushed;
* @param targetFragment [ViewControllerFragment] to be set as target;
* @param state [Parcelable] of [ViewController]'s fragment;
* @param backStackTag Tag of [ViewControllerFragment] in back stack;
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;
* @param TState Type of state of fragment.
*/
protected fun <TState : Parcelable> addViewControllerToStack(
viewControllerClass: Class<out ViewController<TActivity, out ViewControllerFragment<TState, TActivity>, TState>>,
targetFragment: Fragment?,
addToStack: Boolean,
state: TState,
backStackTag: String?,
transactionSetup: Function<FragmentTransaction, FragmentTransaction>?
) {
addToStack(
ViewControllerFragment::class.java,
targetFragment,
addToStack,
ViewControllerFragment.args(viewControllerClass, state),
backStackTag,
transactionSetup
)
}
}