terrakok / cicerone Goto Github PK
View Code? Open in Web Editor NEW๐ฆ Cicerone is a lightweight library that makes the navigation in an Android app easy.
License: Other
๐ฆ Cicerone is a lightweight library that makes the navigation in an Android app easy.
License: Other
I happen to have the following line in my code, which invokes an Activity
and passing a Map as argument:
router?.navigateTo(RssFeedActivity.TAG, dataMap)
My question is how to retrieve that data once the new Activity
is running (similar to what, for instance, getIntent().geStringExtra("myStringKey")
does when simply using "plain" Android).
Do you have any examples of working examples with child inner fragments?
Cicerone min sdk is 19, like in sample app, or previous APIs also compatible?
Also how do you use showSystemMessage for everything else except toasts? Overriding?
I have an idea. How do you think - is it ok or not?
If we delegate Activity#onBackPressed()
to something Cicerone has access and then notify about it everything local stack copy has, so we can remove this ugly nullable current fragment reference in activity.
Think this will also be good because of synchronized and only-available-fragment nature of navigator.
Please, implement sendResult without exit.
Smth like: router.sendResult(code, object)
(now this method is protected ๐ต)
How to show two fragments in one activity simultaneously using Cicerone?
If I use router.navigateTo() without passing data as a second parameter like this:
App.INSTANCE.router.navigateTo(SCREEN_NAME)
the app crashes with IllegalArgumentException. Part of stacktrace:
Process: com.example.etmkotlin, PID: 4753
java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter data
at com.example.etmkotlin.activities.MainActivity$navigator$1.createFragment(MainActivity.kt)
at ru.terrakok.cicerone.android.SupportFragmentNavigator.forward(SupportFragmentNavigator.java:107)
at ru.terrakok.cicerone.android.SupportFragmentNavigator.applyCommand(SupportFragmentNavigator.java:91)
at ru.terrakok.cicerone.android.SupportFragmentNavigator.applyCommands(SupportFragmentNavigator.java:71)
at ru.terrakok.cicerone.CommandBuffer.executeCommands(CommandBuffer.java:42)
at ru.terrakok.cicerone.BaseRouter.executeCommands(BaseRouter.java:30)
at ru.terrakok.cicerone.Router.navigateTo(Router.java:83)
at ru.terrakok.cicerone.Router.navigateTo(Router.java:73)
at com.example.etmkotlin.activities.MainActivity$override.openScreen(MainActivity.kt:107)
...
Part of Router.java:
/**
* Open new screen and add it to the screens chain.
*
* @param screenKey screen key
*/
public void navigateTo(String screenKey) {
navigateTo(screenKey, null);
}
/**
* Open new screen and add it to screens chain.
*
* @param screenKey screen key
* @param data initialisation parameters for the new screen
*/
public void navigateTo(String screenKey, Object data) {
executeCommands(new Forward(screenKey, data));
}
So if the data is null it will crash.
But if I pass some data for example:
App.INSTANCE.router.navigateTo(SCREEN_NAME, "123")
everything wotks great.
Hi again. I have a BaseActivity and I want to put both fragment and Activity navigators in it, because I want to open fragment/activity from any place/screen of my app. Is this possible?
Suppose I want to navigate to fragment or to other activity from currentActivity.
SampleApplication.INSTANCE.getNavigatorHolder().setNavigator(fragment);
SampleApplication.INSTANCE.getNavigatorHolder().setNavigator(activity); // addNavigator()?
Hi, thanks for the library!
Now I'm trying to use it in my current project, and I've faced the UI 'blink' artifact while replacing the fragments using the SupportFragmentNavigator class provided by the library. So, the issue is:
The blink happens because onViewCreated method is called while replacing B with C, and the keyboard tries to be shown for a moment.
Do you have any recommendations how to handle this situation?
It would be nice to just take fragment from top of chain.
For example
MyFragment fragment = cicerone.getTopFragment();
fragment.foo();
router.navigateTo(EXTRA_NEXT_SCREEN, new Object())
is a great feature and I'm using it in my projects, but sometimes I need to return some data to previous step/fragment, for example, when I need to return token after phone confirm.
It would be great if I can use something like router.backTo(EXTRA_PREVIOUS_SCREEN, new Object())
inside Cicerone routers logic and without necessity of injecting additional interactors to presenters.
Hi,
As it stands, SupportAppNavigator
forces support for both Activity and Fragments when implementing it. If I'm refraining from using fragments altogether, then it doesn't make as much sense for me to put in a containerId
(which needs a primitive int
value) and to override createFragment
and pass back an arbitrary non-null Fragment
.
While it is easy enough to override the Navigator
interface and build my own Activity-only navigator, it would be nice if we could have one in the library ready to use, out-of-the-box. Could contribute this myself too. Thoughts?
Do you have any examples of working with activities or this lib is suitable only to work with fragments?
First of all, nice library. It's been helping me a lot.
I'm having a problem in a setup similar to the BottomNavigationActivity of the sample.
I want to have a slide animation when navigating inside a tab (opening forwardFragments) and no animation or some other animation when I switch tabs. The problem is: when I switch tabs, the parent fragment recreates the child and this forces the animation that was previously set up, without even calling setupFragmentTransactionAnimation() again, to give me a chance to disable or change the animation. This is my guess of what's happening, at least.
To reproduce this, simply add any animation to the TabContainerFragment's Navigator. Then navigate inside a tab, and then between tabs.
Am I misusing the library, or is this a bug ? Could we have a method that is always called when a transaction will happen ?
Thanks !
I have activity A that opens Fragment A1 and then Fragment A2. And when i want to navigate to activity B, how can i remove fragment A2 from back stack without screen blinking , so when i back from activity B to activity A there would be only fragment A1 ?
Hello,
Is there a listener/callback which can be attached to Cicerone, which would trigger when there was a screen change?
If not, is there a method which will return the current visible screen, or the backstack?
Thanks for the great lib!
The usecase:
I've got an app with nested navigation like in your example app. Got several instances of the TabContainerFragment wich could be selected by tapping on bottom navigation bar's icons.
I overrided setupFragmentTransactionAnimation method in TabContainerFragment's navigator and described transition from fragment A to fragment B with animation xml files.
Steps:
Let's say in tab 1 I move forward from fragment A to fragment B. Animation works perfectly. After that I select tab 2 that contains fragment C and return back to tab 1, that has fragment B. At this point fragment B appears with the last applied animation again. But I don't want to see this animation when I navigating between the tabs. How can I disable it?
Thanks in advance!
I want to implement my navigator in separate class to make Activity's code look clearer. I need activity instance for createActivityIntent method. But activity class variable in AppNavigator is private. So i think it would be better to make it protected because now I have to declare another activity variable for my class and it looks really bad.
Readme says: "It (Cicerone) was designed to be used with the MVP pattern (try Moxy), but will work great with any architecture."
Is there any tutorial how to use Cicerone without an MVP architecture? Even the Cicerone library includes Moxy as a dependency. Is there a way to make Cicerone a bit more loosely coupled?
Javadoc for method tells us
Called when we tried to back to some specific screen, but didn't found it.
According to the commentary, for me it would be logical to pass the screen key to it.
In some cases, I need more control in a subclass in this case.
In my Android application I'm writing splash activity screen with makes transition to the core bottom navigation activty with one second delay. In my transition I want to replace splash screen and remove it from backstack.
For this purpose I wrote such code:
https://gist.github.com/LAHomieJob/aa81ae68309d73e2fa73319c1b46c5d5
My problem is that router.replaceScreen behaves like Router method navigateTo - SplashActivity remains inside in back stack and I can reach it via back button
What's wrong with my code?
As said in https://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume():
Note that for better inter-operation with older versions of the platform, at the point of this call the fragments attached to the activity are not resumed. This means that in some cases the previous state may still be saved, not allowing fragment transactions that modify the state. To correctly interact with fragments in their proper state, you should instead override onResumeFragments().
Hi!
Thank you for good tool.
It would be so good to add "to the box" function to check, if current screen is root.
At now I'm extending Support(App|Fragment)Navigator with function "isRootScreen". I think, it is useful function.
Is it right to inject router in presenter with dagger and moxy? Or more correct way is injecting in fragment and providing to presenter?
What the difference?
I think we need to rename Router.exit()
method. It constantly causes misunderstanding from new people.
My proposal is back()
as most intuitive one.
Just want to set animation between activities. Could you please help with this?
I'm wondering if there is a way to alter the FragmentTransaction
instance, which is being used in the forward(Forward command)
method, as seen in the SupportFragmentNavigator
class. I need to fragmentTransaction.add()
fragments instead of fragmentTransaction.replace()
-ing them.
I'm using the navigateTo
method to show the next Fragment, like so:
App.get().getRouter().navigateTo(Screens.CUSTOMER_DETAIL, customer);
I have a Fragment which basically hosts a RecyclerView
with a lots of data (customers), returned by remote API.
Clicking an item from that list will open a new screen which will host detailed data for that customer.
A common scenario you might guess.
When I go back from the detail screen, the API is invoked again, the list reloaded, scrolling position gone, search filters invalidated etc.
That's because the library uses fragmentTransaction.replace()
, but fragmentTransaction.add()
is what I need, to avoid the reload.
I've copied locally these two classes: SupportFragmentNavigator
and SupportAppNavigator
and changed what I need, and it works.
But it's a dirty solution.
Is there an API in Cicerone which will provide customization options for this manner?
I believe this would make a better API in terms of composing several navigators.
It would be more natural to do something like this:
class TopNavigator(private val childNavigator: Navigator) : Navigator {
fun applyCommand(command: Command): Boolean {
if (childNavigator.applyCommand(command)) {
// child has handled this command, great!
} else {
handleCommandInTopNavigator()
}
}
With the current implementation returning "void" it is not simple to write above logic, you'd have to resort to some "hacky" ways.
If you are not against this suggestion, I could prepare a PR.
/**
* Clear current stack and open several screens inside single transaction.
* @param screens
*/
public void newRootChain(Screen... screens) {
Command[] commands = new Command[screens.length + 1];
commands[0] = new BackTo(null);
for (int i = 0; i < commands.length; i++) {
commands[i + 1] = new Forward(screens[i]);
}
executeCommands(commands);
}
This function always results in ArrayIndexOutOfBoundsException
because of iteration comparison with commands.lenght
, and it should compare to screens.lenght
In my app I make transitions between activities with the help of Cicerone library. The code of my activity is https://gist.github.com/LAHomieJob/d786355666795a8f58b8e7975d302e49#file-choosecallactivity-kt-L152
I want to pass intent with "phoneCallRepresentation" parcelable object, which will be known only during runtime (it depends on selection from recyclerView). I don't know how to do it. Do I need to write putExtra to intent? Or I need to pass this object only in router.navigateTo() as a parameter?
The example was broken after last commits in the library.
In MainActivity and TabContainerFragment there is constructor error in SupportFragmentNavigator.
Hello! I need to transfer the data to the previous activity screen, because the current screen was launched with the command onActivityScreen. How can i do this? If for example the command navigateTo allows me to do this, then exit() not have an object parameter for data transfer. Thanks in advance for the answer.
I've got an exception when trying set Target Fragment (Android 7.0, Samsung J-330), support version - 27.1.0
BRAND_SELECTION_FRAGMENT -> {
val fragment = BrandSelectionFragment.newInstance(data as ArrayList<StationNetwork>)
fragment.setTargetFragment(filterFragment, BRAND_SELECTION_REQUEST_CODE)
fragment
}
java.lang.IllegalStateException: Fragment BrandSelectionFragment{99d4bb6 #1 id=0x7f0a00c8} declared target fragment FilterFragment{9cee0b7} that does not belong to this FragmentManager!
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1354)
at android.support.v4.app.FragmentTransition.addToFirstInLastOut(FragmentTransition.java:1188)
at android.support.v4.app.FragmentTransition.calculateFragments(FragmentTransition.java:1071)
at android.support.v4.app.FragmentTransition.startTransitions(FragmentTransition.java:115)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2379)
at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2337)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2244)
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:702)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6776)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1496)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1386)
is this still Android bug and what could be the steps to fix it?
The code sample-cicerone, and change this:
public void onForwardWithDelayCommandClick() {
if (future != null) future.cancel(true);
future = executorService.schedule(new Runnable() {
@Override
public void run() {
router.showSystemMessage("CHECK");
router.exit();
//router.navigateTo(Screens.SAMPLE_SCREEN + (screenNumber + 1), screenNumber + 1);
}
}, 5, TimeUnit.SECONDS);
}
Nothing will happen! The methods of the "router" are executed, but no action will occur. And the "navigateTo" method will be executed!
Why do not "exit" and "showSystemMessage" work?
SupportFragmentNavigator
private void backToRoot() {
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
localStackCopy.clear();
}
Shouldn't this pop all fragments off the backstack until we reach the root? Currently, it only pops the top one.
As a result, the following navigation happens:
On: A
Navigate: Forward to B
On: B
Navigate: newScreenChain
starting with A
On: A
Navigate: Back
On: B <--- what?! But we called newScreenChain
!
I've seen in your library that you pass data between views with Object data parametr? Could you give me advice how to pass complex data? For example, I want to pass one Integer and one String, what I need to do?
Activity's OnResume and OnPause looks exactly like this
@Override
protected void onResume(){
super.onResume();
MyApp.INSTANCE.getNavigatorHolder().setNavigator(navigator);
}
@Override
protected void onPause(){
MyApp.INSTANCE.getNavigatorHolder().removeNavigator();
super.onPause();
}
This exception is reported by remote device and I really don't know how reproduce it, so the only information I have is the following stack trace:
Fatal Exception: java.lang.RuntimeException: Unable to resume activity {my.app/my.app.activity.RootActivity}: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3226)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3257)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1514)
at android.os.Handler.dispatchMessage(Handler.java:111)
at android.os.Looper.loop(Looper.java:194)
at android.app.ActivityThread.main(ActivityThread.java:5631)
at java.lang.reflect.Method.invoke(Method.java)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:959)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:754)
Caused by java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1842)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1860)
at android.support.v4.app.FragmentManagerImpl.popBackStack(FragmentManager.java:770)
at ru.terrakok.cicerone.android.SupportFragmentNavigator.backToRoot(SupportFragmentNavigator.java:145)
at ru.terrakok.cicerone.android.SupportFragmentNavigator.backToUnexisting(SupportFragmentNavigator.java:189)
at ru.terrakok.cicerone.android.SupportFragmentNavigator.applyCommand(SupportFragmentNavigator.java:135)
at ru.terrakok.cicerone.CommandBuffer.executeCommand(CommandBuffer.java:43)
at ru.terrakok.cicerone.CommandBuffer.setNavigator(CommandBuffer.java:26)
at my.app.activity.RootActivity.onResume(RootActivity.java:1248)
at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1267)
at android.app.Activity.performResume(Activity.java:6178)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3211)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3257)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1514)
at android.os.Handler.dispatchMessage(Handler.java:111)
at android.os.Looper.loop(Looper.java:194)
at android.app.ActivityThread.main(ActivityThread.java:5631)
at java.lang.reflect.Method.invoke(Method.java)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:959)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:754)
I have two different devices which was affected by this issue:
Philips V377 Android 5.1
UMI X2 Android 4.2.1
So, guys, you have:
private Navigator navigator = new SupportFragmentNavigator(getSupportFragmentManager(), R.id.main_container) {
@Override
protected Fragment createFragment(String screenKey, Object data) {
return SampleFragment.getNewInstance((int) data);
}
}
public static SampleFragment getNewInstance(int number) {
SampleFragment fragment = new SampleFragment();
Bundle args = new Bundle();
args.putInt(EXTRA_NUMBER, number);
fragment.setArguments(args);
return fragment;
}
and then you execute it with a pretty simple command:
navigator.applyCommand(new Replace(Screens.SAMPLE_SCREEN, 1));
But what if I have more than argument?
Lets say 5 of them?
I can create a helper class with this 5 fields, or pass bundle every time I need to open a fragment.
Maybe you have a better way?
I got a question. My Navigation class contains Router. There is no activity context. And for example my ViewModel doesn't know what fragment contains in container right now. It try to set fragment already contains and it replace but new Fragment("Fragment A" replace/forward/etc "Fragment A"). How can I prevent this and do nothing in this case?
How i can open new activity from fragment? I use SupportFragmentNavigator which adding to router in activity.
I suggest setting a modifier protected for fragmentManager and containerId in SupportFragmentNavigator. It's needed for a new command implementation in SupportFragmentNavigator heir.
private HashMap<Integer, ResultListener> resultListeners = new HashMap<>()
;
can be replaced with
private SparseArray<ResultListener> resultListeners = new SparseArray<>();
We should throw by default to fail early.
Currently at 25, well behind 28
Trying to set result listener using this code:
INSTANCE.cicerone.navigatorHolder.setNavigator((activity as MainActivity).navigator) getRouter().setResultListener(Utils.NEW_PROJECT_RESULT_CODE, newProjectResultListener) getRouter().navigateTo(FragmentIdentifiers.DETAILED_PROJECT_FRAGMENT)
but when I trying to use exitWithResult nothing is happens cause the set of result listeners in empty.
How to fix it?
Hi,
Would this lib allow a result to be passed across multiple activities like :
A need result
D produce result
But B is finished or destroyed by android
A -> B (destroyed) -> C -> D
maybe a command like
BackToWithResult(screen, result)
Hi, I've just started using this library and am finding it hard to see what the implementations of Command, Navigator, etc are. There's no API spec that I can find short of crawling through the source myself.
A javadoc would be helpful. Thanks!
This library would need to update references:
android.support.v4.app.Fragment
to androidx.fragment.app.Fragment
android.support.v4.app.FragmentActivity
to androidx.fragment.app.FragmentActivity
among others, to support migrations to androidX packages.
Edit: To be clear, it does currently work with jetifier, but throws up error-level warnings in studio, which always has me checking back to classes to make sure I haven't messed something up.
Maybe we could do another artifact for this, for the sake of compatibility for those who haven't yet updated?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.