Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1701bc4f36 | |||
| 9c9c56bc9b | |||
| 3da8fe52ca | |||
| a943aab08e | |||
| 86ac421fb8 | |||
| f7009f53e8 | |||
| 994c497147 | |||
| bb13ace1c9 | |||
| dbe065275d | |||
| 2ebbc98a99 | |||
| ba7eea60f5 | |||
| 6347fe7d95 | |||
| 2b6e2ee874 |
@@ -6,15 +6,39 @@
|
||||
[](http://androidweekly.net/issues/issue-250)
|
||||
[](http://androidweekly.net/issues/issue-271)
|
||||
|
||||

|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="https://github.com/terrakok/Cicerone/raw/master/media/navigation.gif" width="256"/>
|
||||
</td>
|
||||
<td>
|
||||
<img src="https://github.com/terrakok/Cicerone/raw/master/media/insta_tabs.gif" width="256"/>
|
||||
</td>
|
||||
<td>
|
||||
<img src="https://github.com/terrakok/Cicerone/raw/master/media/animations.gif" width="256"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Power navigation
|
||||
</td>
|
||||
<td>
|
||||
Multibackstack
|
||||
</td>
|
||||
<td>
|
||||
Result listeners
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Cicerone (a guide, one who conducts sightseers) is a lightweight library that makes the navigation in an Android app easy.
|
||||
Cicerone (means - a guide, one who conducts sightseers) is a lightweight library that makes the navigation in an Android app easy.
|
||||
It was designed to be used with the MVP/MVVM/MVI patterns but will work great with any architecture.
|
||||
|
||||
## Main advantages
|
||||
+ is not tied to Fragments
|
||||
+ not a framework
|
||||
+ not a framework (very lightweight)
|
||||
+ short navigation calls (no builders)
|
||||
+ static typed checks for screen parameters!
|
||||
+ lifecycle-safe!
|
||||
+ functionality is simple to extend
|
||||
+ suitable for Unit Testing
|
||||
@@ -109,13 +133,13 @@ override fun onPause() {
|
||||
## Navigation commands
|
||||
This commands set will fulfill the needs of the most applications. But if you need something special - just add it!
|
||||
+ Forward - Opens new screen
|
||||

|
||||

|
||||
+ Back - Rolls back the last transition
|
||||

|
||||

|
||||
+ BackTo - Rolls back to the needed screen in the screens chain
|
||||

|
||||

|
||||
+ Replace - Replaces the current screen
|
||||

|
||||

|
||||
|
||||
## Predefined navigator
|
||||
The library provides predefined navigator for _Fragments_ and _Activity_.
|
||||
@@ -147,21 +171,21 @@ private val navigator = object : AppNavigator(this, R.id.container) {
|
||||
Describe your screens as you like e.g. create Kotlin `object` with all application screens:
|
||||
```kotlin
|
||||
object Screens {
|
||||
val Main = FragmentScreen("MainFragment") { MainFragment() }
|
||||
val AddressSearch = FragmentScreen("AddressSearchFragment") { AddressSearchFragment() }
|
||||
fun Profile(userId: Long) = FragmentScreen("ProfileFragment") { ProfileFragment(userId) }
|
||||
fun Main() = FragmentScreen { MainFragment() }
|
||||
fun AddressSearch() = FragmentScreen { AddressSearchFragment() }
|
||||
fun Profile(userId: Long) = FragmentScreen("Profile_$userId") { ProfileFragment(userId) }
|
||||
}
|
||||
```
|
||||
|
||||
Additional you can use `FragmentFactory` for creating your screens:
|
||||
```kotlin
|
||||
val SomeScreen = FragmentScreen("SomeScreenId") { factory: FragmentFactory -> ... }
|
||||
fun SomeScreen() = FragmentScreen { factory: FragmentFactory -> ... }
|
||||
```
|
||||
|
||||
## Screen parameters and result listener
|
||||
```kotlin
|
||||
//you have to specify screen parameters via new FragmentScreen creation
|
||||
fun SelectPhoto(resultKey: String) = FragmentScreen("SelectPhoto") {
|
||||
fun SelectPhoto(resultKey: String) = FragmentScreen {
|
||||
SelectPhotoFragment.getNewInstance(resultKey)
|
||||
}
|
||||
```
|
||||
@@ -188,26 +212,15 @@ To see how to add, initialize and use the library and predefined navigators see
|
||||
|
||||
For more complex use case check out the [GitFox (Android GitLab client)](https://gitlab.com/terrakok/gitlab-client)
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## Applications with Cicerone inside
|
||||
<a href="https://play.google.com/store/apps/details?id=ru.foodfox.client" target="_blank"><img src="https://lh3.googleusercontent.com/m_8FvRusYXPUNhoP4dqLUrOjaLvXnGSNc8gXd2p-QlzO1vQZV4RBiYxXFoY8wgSnggA" width="256" height="125" alt="Яндекс.Еда — доставка еды/продуктов. Food delivery" /></a><br>
|
||||
Яндекс.Еда — доставка еды/продуктов. Food delivery
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=com.foodient.whisk" target="_blank"><img src="https://i.ytimg.com/vi/DSqp6tJkKkI/hqdefault.jpg" width="256" height="192" alt="Whisk: Recipe Saver, Meal Planner & Grocery List" /></a><br>
|
||||
Whisk: Recipe Saver, Meal Planner & Grocery List
|
||||
|
||||
<a href="https://github.com/eduard1abdulmanov123/News" target="_blank"><img src="https://raw.githubusercontent.com/eduard1abdulmanov123/News/dev/screenshots/1.1%20%D0%B4%D0%BB%D1%8F%20%D0%B2%D0%B5%D1%81%D1%82%D0%B8.%D1%80%D1%83.jpg" width="256" height="125" alt="RSS Reader для Вести.Ru" /></a><br>
|
||||
RSS Reader для Вести.Ru
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=com.epam.connect.android" target="_blank"><img src="https://lh3.googleusercontent.com/GLMJhNoY37C1RfiwxHb-VBsCu0PgHSVhSmNWStisuSUBt_vUTmEGW4slERP-MgKqmqI" width="256" height="125" alt="EPAM Connect" /></a><br>
|
||||
EPAM Connect
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=org.consumerreports.ratings" target="_blank"><img src="https://freesiteslike.com/wp-content/uploads/2017/05/consumer-reports.png" width="256" height="144" alt="Consumer Reports: Product Reviews & Ratings" /></a><br>
|
||||
Consumer Reports: Product Reviews & Ratings
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=ru.foodfox.client"><img src="https://play-lh.googleusercontent.com/gWYedIqy8QujCQOn0kzEIBEkGLBSpuKvFm-fMcfkWnJ1Oirtv847xAE4OyhAaohdcp5V=s360" width="64" /> Яндекс.Еда — доставка еды/продуктов. Food delivery</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.foodient.whisk"><img src="https://play-lh.googleusercontent.com/eKotZjJcZOU2_L9t2l34EEY7aGl5zhvKVuEbF0Kc4MRs_pAC2SJgOnWMkMTFjR_e9EY=s360" width="64" /> Whisk: Recipe Saver, Meal Planner & Grocery List</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=kz.beeline.odp"><img src="https://play-lh.googleusercontent.com/hzgjpQQpy6Z-Byye0aVKSv9P7h8yx58i6pVkQtiM6jB99iWFXjYfKeaPqJ3wm6Rtb38=s360" width="64" /> Мой Beeline (Казахстан)</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.mercuryo.app"><img src="https://play-lh.googleusercontent.com/FKulXdc15r5PWX6hTZi2i3iaJjcQHwd9xParp6YPiQ2KiBqza7jwEt_b_tqLwXpyEHg=s360" width="64" /> Mercuryo Bitcoin Cryptowallet</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.warefly.checkscan"><img src="https://play-lh.googleusercontent.com/2c2uuiSl2vwGgp-vdI-VArQEMdSSXk1neUK5A-Udc0WANPcvp5kBJFEugrFiXnxUc7k=s360" width="64" /> ЧекСкан - кэшбэк за чеки, цены и акции в магазинах</a><br>
|
||||
<a href="https://github.com/eduard1abdulmanov123/News"><img src="https://raw.githubusercontent.com/eduard1abdulmanov123/News/dev/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png" width="64" /> RSS Reader для Вести.Ru</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.epam.connect.android"><img src="https://play-lh.googleusercontent.com/aN7R6BiR7yt7b3oEoBI30pVwzsdzaWe3TWpw8c9igqoOj79Pm2xVh4_C4qwjSKwjVio=s360" width="64" /> EPAM Connect</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=org.consumerreports.ratings"><img src="https://play-lh.googleusercontent.com/dEdOwZOjXAdamytxY1TgY8LS-Hc9FKCcit5HP1RyaKqRAWjDJEyFSQS1XlqQPpeY5UI=s360" width="64" /> Consumer Reports: Product Reviews & Ratings</a><br>
|
||||
|
||||
## Participants
|
||||
+ idea and code - Konstantin Tskhovrebov (@terrakok)
|
||||
|
||||
@@ -18,7 +18,7 @@ ext {
|
||||
bintrayName = 'cicerone-kotlin'
|
||||
publishedGroupId = 'com.github.terrakok'
|
||||
artifact = 'cicerone'
|
||||
libraryVersion = '6.3'
|
||||
libraryVersion = '6.5'
|
||||
gitUrl = 'https://github.com/terrakok/Cicerone'
|
||||
allLicenses = ['MIT']
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ abstract class BaseRouter {
|
||||
*
|
||||
* After first call listener will be removed.
|
||||
*/
|
||||
fun setResultListener(key: String, listener: (data: Any) -> Unit) {
|
||||
fun setResultListener(key: String, listener: ResultListener) {
|
||||
resultWire.setResultListener(key, listener)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,14 @@ class Cicerone<T : BaseRouter> private constructor(val router: T) {
|
||||
/**
|
||||
* Creates the Cicerone instance with the default [Router]
|
||||
*/
|
||||
@JvmStatic
|
||||
fun create() = create(Router())
|
||||
|
||||
/**
|
||||
* Creates the Cicerone instance with the custom router.
|
||||
* @param customRouter the custom router extending [BaseRouter]
|
||||
*/
|
||||
@JvmStatic
|
||||
fun <T : BaseRouter> create(customRouter: T) = Cicerone(customRouter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,23 @@ package com.github.terrakok.cicerone
|
||||
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
internal class ResultWire {
|
||||
private val listeners = mutableMapOf<String, WeakReference<(Any) -> Unit>>()
|
||||
/**
|
||||
* Interface definition for a result callback.
|
||||
*/
|
||||
fun interface ResultListener {
|
||||
fun onResult(data: Any)
|
||||
}
|
||||
|
||||
fun setResultListener(key: String, listener: (data: Any) -> Unit) {
|
||||
internal class ResultWire {
|
||||
private val listeners = mutableMapOf<String, WeakReference<ResultListener>>()
|
||||
|
||||
fun setResultListener(key: String, listener: ResultListener) {
|
||||
listeners[key] = WeakReference(listener)
|
||||
}
|
||||
|
||||
fun sendResult(key: String, data: Any) {
|
||||
listeners.remove(key)?.get()?.let { listener ->
|
||||
listener(data)
|
||||
listener.onResult(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ open class Router : BaseRouter() {
|
||||
* @param screen screen
|
||||
* @param clearContainer if FALSE then new screen shows over previous
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun navigateTo(screen: Screen, clearContainer: Boolean = true) {
|
||||
executeCommands(Forward(screen, clearContainer))
|
||||
}
|
||||
@@ -58,6 +59,7 @@ open class Router : BaseRouter() {
|
||||
* @param screens
|
||||
* @param showOnlyTopScreenView if FALSE then all screen views show together
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun newChain(vararg screens: Screen, showOnlyTopScreenView: Boolean = true) {
|
||||
val commands = screens.map { Forward(it, showOnlyTopScreenView) }
|
||||
executeCommands(*commands.toTypedArray())
|
||||
@@ -69,6 +71,7 @@ open class Router : BaseRouter() {
|
||||
* @param screens
|
||||
* @param showOnlyTopScreenView if FALSE then all screen views show together
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun newRootChain(vararg screens: Screen, showOnlyTopScreenView: Boolean = true) {
|
||||
val commands = screens.mapIndexed { index, screen ->
|
||||
if (index == 0)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.github.terrakok.cicerone.androidx
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import androidx.fragment.app.*
|
||||
import com.github.terrakok.cicerone.*
|
||||
@@ -13,11 +14,11 @@ import com.github.terrakok.cicerone.androidx.TransactionInfo.Type.REPLACE
|
||||
*
|
||||
* Recommendation: most useful for Single-Activity application.
|
||||
*/
|
||||
open class AppNavigator constructor(
|
||||
open class AppNavigator @JvmOverloads constructor(
|
||||
protected val activity: FragmentActivity,
|
||||
protected val containerId: Int,
|
||||
protected val fragmentManager: FragmentManager = activity.supportFragmentManager,
|
||||
protected val fragmentFactory: FragmentFactory = FragmentFactory()
|
||||
protected val fragmentFactory: FragmentFactory = fragmentManager.fragmentFactory
|
||||
) : Navigator {
|
||||
|
||||
protected val localStackCopy = mutableListOf<TransactionInfo>()
|
||||
@@ -109,6 +110,7 @@ open class AppNavigator constructor(
|
||||
) {
|
||||
val fragment = screen.createFragment(fragmentFactory)
|
||||
val transaction = fragmentManager.beginTransaction()
|
||||
transaction.setReorderingAllowed(true)
|
||||
setupFragmentTransaction(
|
||||
transaction,
|
||||
fragmentManager.findFragmentById(containerId),
|
||||
@@ -170,9 +172,9 @@ open class AppNavigator constructor(
|
||||
private fun checkAndStartActivity(screen: ActivityScreen) {
|
||||
// Check if we can start activity
|
||||
val activityIntent = screen.createIntent(activity)
|
||||
if (activityIntent.resolveActivity(activity.packageManager) != null) {
|
||||
try {
|
||||
activity.startActivity(activityIntent, screen.startActivityOptions)
|
||||
} else {
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
unexistingActivity(screen, activityIntent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,17 +9,23 @@ import com.github.terrakok.cicerone.Screen
|
||||
|
||||
sealed class AppScreen : Screen
|
||||
|
||||
open class FragmentScreen(
|
||||
private val key: String? = null,
|
||||
val createFragment: (FragmentFactory) -> Fragment
|
||||
) : AppScreen() {
|
||||
override val screenKey: String get() = key ?: super.screenKey
|
||||
fun interface Creator<A, R> {
|
||||
fun create(argument: A): R
|
||||
}
|
||||
|
||||
open class ActivityScreen(
|
||||
open class FragmentScreen @JvmOverloads constructor(
|
||||
private val key: String? = null,
|
||||
val createIntent: (context: Context) -> Intent
|
||||
private val fragmentCreator: Creator<FragmentFactory, Fragment>
|
||||
) : AppScreen() {
|
||||
override val screenKey: String get() = key ?: super.screenKey
|
||||
fun createFragment(factory: FragmentFactory) = fragmentCreator.create(factory)
|
||||
}
|
||||
|
||||
open class ActivityScreen @JvmOverloads constructor(
|
||||
private val key: String? = null,
|
||||
private val intentCreator: Creator<Context, Intent>
|
||||
) : AppScreen() {
|
||||
override val screenKey: String get() = key ?: super.screenKey
|
||||
open val startActivityOptions: Bundle? = null
|
||||
fun createIntent(context: Context) = intentCreator.create(context)
|
||||
}
|
||||
@@ -12,6 +12,7 @@ data class TransactionInfo(
|
||||
override fun toString() = screenKey + type.symbol
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromString(str: String) = TransactionInfo(
|
||||
str.dropLast(1),
|
||||
if (str.last() == Type.ADD.symbol) Type.ADD else Type.REPLACE
|
||||
|
||||
@@ -36,6 +36,10 @@ public class FragmentManager {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public FragmentFactory getFragmentFactory() {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public interface BackStackEntry {
|
||||
int getId();
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ package androidx.fragment.app;
|
||||
*/
|
||||
|
||||
public class FragmentTransaction {
|
||||
public FragmentTransaction setReorderingAllowed(boolean reorderingAllowed) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ class ProfileActivity : AppCompatActivity() {
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
private val navigator: Navigator = object : AppNavigator(this, R.id.container, supportFragmentManager, supportFragmentManager.fragmentFactory) {
|
||||
private val navigator: Navigator = object : AppNavigator(this, R.id.container) {
|
||||
|
||||
override fun setupFragmentTransaction(fragmentTransaction: FragmentTransaction, currentFragment: Fragment?, nextFragment: Fragment?) {
|
||||
if (currentFragment is ProfileFragment
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ class BottomNavigationActivity : MvpAppCompatActivity(), BottomNavigationView, R
|
||||
if (newFragment == null) {
|
||||
transaction.add(
|
||||
R.id.ab_container,
|
||||
Tab(tab).createFragment.invoke(fm.fragmentFactory), tab
|
||||
Tab(tab).createFragment(fm.fragmentFactory), tab
|
||||
)
|
||||
}
|
||||
if (currentFragment != null) {
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ import javax.inject.Inject
|
||||
class TabContainerFragment : Fragment(), RouterProvider, BackButtonListener {
|
||||
|
||||
private val navigator: Navigator by lazy {
|
||||
AppNavigator(activity!!, R.id.ftc_container, childFragmentManager, childFragmentManager.fragmentFactory)
|
||||
AppNavigator(activity!!, R.id.ftc_container, childFragmentManager)
|
||||
}
|
||||
|
||||
@Inject
|
||||
|
||||
@@ -31,7 +31,7 @@ class MainActivity : MvpAppCompatActivity(), ChainHolder {
|
||||
@Inject
|
||||
lateinit var navigatorHolder: NavigatorHolder
|
||||
|
||||
private val navigator: Navigator = object : AppNavigator(this, R.id.main_container, supportFragmentManager, supportFragmentManager.fragmentFactory) {
|
||||
private val navigator: Navigator = object : AppNavigator(this, R.id.main_container) {
|
||||
|
||||
override fun applyCommands(commands: Array<out Command>) {
|
||||
super.applyCommands(commands)
|
||||
|
||||
@@ -29,7 +29,7 @@ class StartActivity : MvpAppCompatActivity(), StartActivityView {
|
||||
@InjectPresenter
|
||||
lateinit var presenter: StartActivityPresenter
|
||||
|
||||
private val navigator: Navigator = AppNavigator(this, -1, supportFragmentManager, supportFragmentManager.fragmentFactory)
|
||||
private val navigator: Navigator = AppNavigator(this, -1)
|
||||
|
||||
@ProvidePresenter
|
||||
fun createStartActivityPresenter() = StartActivityPresenter(router)
|
||||
|
||||
Reference in New Issue
Block a user