Instantly share code, notes, and snippets.
davidwhitman/CiceroneSupportAppNavigator.kt
Created Sep 28, 2018
| import androidx.fragment.app.Fragment | |
| import androidx.fragment.app.FragmentManager | |
| import androidx.fragment.app.FragmentTransaction | |
| import ru.terrakok.cicerone.Navigator | |
| import ru.terrakok.cicerone.commands.* | |
| import java.util.* | |
| /** | |
| * [Navigator] implementation based on the support fragments. | |
| * | |
| * | |
| * [BackTo] navigation command will return to the root if | |
| * needed screen isn't found in the screens chain. | |
| * To change this behavior override [.backToUnexisting] method. | |
| * | |
| * | |
| * | |
| * [Back] command will call [.exit] method if current screen is the root. | |
| * | |
| * | |
| * Created by Konstantin Tskhovrebov (aka @terrakok) | |
| * | |
| * Migrated to AndroidX (and Kotlin) by David Whitman. Based on v3.0.0. | |
| * | |
| */ | |
| abstract class CiceroneSupportFragmentNavigator | |
| /** | |
| * Creates SupportFragmentNavigator. | |
| * | |
| * @param fragmentManager support fragment manager | |
| * @param containerId id of the fragments container layout | |
| */ | |
| (private val fragmentManager: FragmentManager, private val containerId: Int) : Navigator { | |
| protected var localStackCopy: LinkedList<String?> = LinkedList() | |
| /** | |
| * Override this method to setup custom fragment transaction animation. | |
| * | |
| * @param command current navigation command. Will be only [Forward] or [Replace] | |
| * @param currentFragment current fragment in container | |
| * (for [Replace] command it will be screen previous in new chain, NOT replaced screen) | |
| * @param nextFragment next screen fragment | |
| * @param fragmentTransaction fragment transaction | |
| */ | |
| protected open fun setupFragmentTransactionAnimation( | |
| command: Command, | |
| currentFragment: Fragment?, | |
| nextFragment: Fragment, | |
| fragmentTransaction: FragmentTransaction | |
| ) { | |
| } | |
| override fun applyCommands(commands: Array<Command>) { | |
| fragmentManager.executePendingTransactions() | |
| //copy stack before apply commands | |
| copyStackToLocal() | |
| for (command in commands) { | |
| applyCommand(command) | |
| } | |
| } | |
| private fun copyStackToLocal() { | |
| localStackCopy = LinkedList() | |
| val stackSize = fragmentManager.backStackEntryCount | |
| for (i in 0 until stackSize) { | |
| localStackCopy.add(fragmentManager.getBackStackEntryAt(i).name) | |
| } | |
| } | |
| /** | |
| * Perform transition described by the navigation command | |
| * | |
| * @param command the navigation command to apply | |
| */ | |
| protected open fun applyCommand(command: Command) { | |
| if (command is Forward) { | |
| forward(command) | |
| } else if (command is Back) { | |
| back() | |
| } else if (command is Replace) { | |
| replace(command) | |
| } else if (command is BackTo) { | |
| backTo(command) | |
| } else if (command is SystemMessage) { | |
| showSystemMessage(command.message) | |
| } | |
| } | |
| /** | |
| * Performs [Forward] command transition | |
| */ | |
| protected open fun forward(command: Forward) { | |
| val fragment = createFragment(command.screenKey, command.transitionData) | |
| if (fragment == null) { | |
| unknownScreen(command) | |
| return | |
| } | |
| val fragmentTransaction = fragmentManager.beginTransaction() | |
| setupFragmentTransactionAnimation( | |
| command, | |
| fragmentManager.findFragmentById(containerId), | |
| fragment, | |
| fragmentTransaction | |
| ) | |
| fragmentTransaction | |
| .replace(containerId, fragment) | |
| .addToBackStack(command.screenKey) | |
| .commit() | |
| localStackCopy.add(command.screenKey) | |
| } | |
| /** | |
| * Performs [Back] command transition | |
| */ | |
| protected fun back() { | |
| if (localStackCopy.size > 0) { | |
| fragmentManager.popBackStack() | |
| localStackCopy.pop() | |
| } else { | |
| exit() | |
| } | |
| } | |
| /** | |
| * Performs [Replace] command transition | |
| */ | |
| protected open fun replace(command: Replace) { | |
| val fragment = createFragment(command.screenKey, command.transitionData) | |
| if (fragment == null) { | |
| unknownScreen(command) | |
| return | |
| } | |
| if (localStackCopy.size > 0) { | |
| fragmentManager.popBackStack() | |
| localStackCopy.pop() | |
| val fragmentTransaction = fragmentManager.beginTransaction() | |
| setupFragmentTransactionAnimation( | |
| command, | |
| fragmentManager.findFragmentById(containerId), | |
| fragment, | |
| fragmentTransaction | |
| ) | |
| fragmentTransaction | |
| .replace(containerId, fragment) | |
| .addToBackStack(command.screenKey) | |
| .commit() | |
| localStackCopy.add(command.screenKey) | |
| } else { | |
| val fragmentTransaction = fragmentManager.beginTransaction() | |
| setupFragmentTransactionAnimation( | |
| command, | |
| fragmentManager.findFragmentById(containerId), | |
| fragment, | |
| fragmentTransaction | |
| ) | |
| fragmentTransaction | |
| .replace(containerId, fragment) | |
| .commit() | |
| } | |
| } | |
| /** | |
| * Performs [BackTo] command transition | |
| */ | |
| protected fun backTo(command: BackTo) { | |
| val key = command.screenKey | |
| if (key == null) { | |
| backToRoot() | |
| } else { | |
| val index = localStackCopy.indexOf(key) | |
| val size = localStackCopy.size | |
| if (index != -1) { | |
| for (i in 1 until size - index) { | |
| localStackCopy.pop() | |
| } | |
| fragmentManager.popBackStack(key, 0) | |
| } else { | |
| backToUnexisting(command.screenKey) | |
| } | |
| } | |
| } | |
| private fun backToRoot() { | |
| fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) | |
| localStackCopy.clear() | |
| } | |
| /** | |
| * Creates Fragment matching `screenKey`. | |
| * | |
| * @param screenKey screen key | |
| * @param data initialization data | |
| * @return instantiated fragment for the passed screen key | |
| */ | |
| protected abstract fun createFragment(screenKey: String, data: Any?): Fragment? | |
| /** | |
| * Shows system message. | |
| * | |
| * @param message message to show | |
| */ | |
| protected abstract fun showSystemMessage(message: String) | |
| /** | |
| * Called when we try to back from the root. | |
| */ | |
| protected abstract fun exit() | |
| /** | |
| * Called when we tried to back to some specific screen (via [BackTo] command), | |
| * but didn't found it. | |
| * @param screenKey screen key | |
| */ | |
| protected fun backToUnexisting(screenKey: String) { | |
| backToRoot() | |
| } | |
| /** | |
| * Called if we can't create a screen. | |
| */ | |
| protected fun unknownScreen(command: Command) { | |
| throw RuntimeException("Can't create a screen for passed screenKey.") | |
| } | |
| } |
| import android.app.Activity | |
| import android.content.Context | |
| import android.content.Intent | |
| import android.os.Bundle | |
| import android.widget.Toast | |
| import androidx.fragment.app.FragmentActivity | |
| import androidx.fragment.app.FragmentManager | |
| import ru.terrakok.cicerone.android.SupportFragmentNavigator | |
| import ru.terrakok.cicerone.commands.BackTo | |
| import ru.terrakok.cicerone.commands.Command | |
| import ru.terrakok.cicerone.commands.Forward | |
| import ru.terrakok.cicerone.commands.Replace | |
| /** | |
| * Extends [SupportFragmentNavigator] to allow | |
| * open new or replace current activity. | |
| * | |
| * | |
| * This navigator DOESN'T provide full featured Activity navigation, | |
| * but can ease Activity start or replace from current navigator. | |
| * | |
| * | |
| * Created by Konstantin Tskhovrebov (aka @terrakok) | |
| * | |
| * Migrated to AndroidX (and Kotlin) by David Whitman. Based on v3.0.0. | |
| */ | |
| abstract class CiceroneSupportAppNavigator : CiceroneSupportFragmentNavigator { | |
| private val activity: Activity | |
| constructor(activity: FragmentActivity, containerId: Int) : super( | |
| activity.supportFragmentManager, | |
| containerId | |
| ) { | |
| this.activity = activity | |
| } | |
| constructor(activity: FragmentActivity, fragmentManager: FragmentManager, containerId: Int) : super( | |
| fragmentManager, | |
| containerId | |
| ) { | |
| this.activity = activity | |
| } | |
| /** | |
| * Override this method to create option for start activity | |
| * | |
| * @param command current navigation command. Will be only [Forward] or [Replace] | |
| * @param activityIntent activity intent | |
| * @return transition options | |
| */ | |
| protected open fun createStartActivityOptions(command: Command, activityIntent: Intent): Bundle? { | |
| return null | |
| } | |
| override fun forward(command: Forward) { | |
| val activityIntent = createActivityIntent(activity, command.screenKey, command.transitionData) | |
| // Start activity | |
| if (activityIntent != null) { | |
| val options = createStartActivityOptions(command, activityIntent) | |
| checkAndStartActivity(command.screenKey, activityIntent, options) | |
| } else { | |
| super.forward(command) | |
| } | |
| } | |
| override fun replace(command: Replace) { | |
| val activityIntent = createActivityIntent(activity, command.screenKey, command.transitionData) | |
| // Replace activity | |
| if (activityIntent != null) { | |
| val options = createStartActivityOptions(command, activityIntent) | |
| checkAndStartActivity(command.screenKey, activityIntent, options) | |
| activity!!.finish() | |
| } else { | |
| super.replace(command) | |
| } | |
| } | |
| private fun checkAndStartActivity(screenKey: String, activityIntent: Intent, options: Bundle?) { | |
| // Check if we can start activity | |
| if (activityIntent.resolveActivity(activity!!.packageManager) != null) { | |
| activity!!.startActivity(activityIntent, options) | |
| } else { | |
| unexistingActivity(screenKey, activityIntent) | |
| } | |
| } | |
| /** | |
| * Called when there is no activity to open `screenKey`. | |
| * | |
| * @param screenKey screen key | |
| * @param activityIntent intent passed to start Activity for the `screenKey` | |
| */ | |
| protected fun unexistingActivity(screenKey: String, activityIntent: Intent) { | |
| // Do nothing by default | |
| } | |
| /** | |
| * Creates Intent to start Activity for `screenKey`. | |
| * | |
| * | |
| * **Warning:** This method does not work with [BackTo] command. | |
| * | |
| * | |
| * @param screenKey screen key | |
| * @param data initialization data, can be null | |
| * @return intent to start Activity for the passed screen key | |
| */ | |
| protected abstract fun createActivityIntent(context: Context, screenKey: String, data: Any?): Intent? | |
| override fun showSystemMessage(message: String) { | |
| // Toast by default | |
| Toast.makeText(activity, message, Toast.LENGTH_SHORT).show() | |
| } | |
| override fun exit() { | |
| // Finish by default | |
| activity!!.finish() | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment