Giter Site home page Giter Site logo

conductor's Introduction

GitHub Actions Workflow Android Arsenal Javadocs

Conductor

A small, yet full-featured framework that allows building View-based Android applications. Conductor provides a light-weight wrapper around standard Android Views that does just about everything you'd want:

Conductor
🎉 Easy integration
☝️ Single Activity apps without using Fragments
♻️ Simple but powerful lifecycle management
🚋 Navigation and backstack handling
🔀 Beautiful transitions between views
💾 State persistence
☎️ Callbacks for onActivityResult, onRequestPermissionsResult, etc
🏤 MVP / MVVM / MVI / VIPER / MVC ready

Conductor is architecture-agnostic and does not try to force any design decisions on the developer. We here at BlueLine Labs tend to use either MVP or MVVM, but it would work equally well with standard MVC or whatever else you want to throw at it.

Installation

Conductor 4.0 is coming soon. It is already being used in production with many, many millions of users. It is, however, not guaranteed to be API stable. As such, it is being released as a preview rather than a standard release. Preview in this context is not a commentary on stability. It is considered to be up to the same quality standards as the current 3.x stable release. Changes in Conductor 4 are available in the GitHub releases. In preparation for the release of the next version, there are currently 3 installation options:

Latest Stable 3.x

def conductorVersion = '3.2.0'

implementation "com.bluelinelabs:conductor:$conductorVersion"

// AndroidX Transition change handlers:
implementation "com.bluelinelabs:conductor-androidx-transition:$conductorVersion"

// ViewPager PagerAdapter:
implementation "com.bluelinelabs:conductor-viewpager:$conductorVersion"

// ViewPager2 Adapter:
implementation "com.bluelinelabs:conductor-viewpager2:$conductorVersion"

4.0 Preview

Use 4.0.0-preview-4 as your version number in any of the dependencies above.

SNAPSHOT

Use 4.0.0-SNAPSHOT as your version number in any of the dependencies above and add the url to the snapshot repository:

allprojects {
  repositories {
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
  }
}

Components to Know

Conductor Components
Controller The Controller is the View wrapper that will give you all of your lifecycle management features. Think of it as a lighter-weight and more predictable Fragment alternative with an easier to manage lifecycle.
Router A Router implements navigation and backstack handling for Controllers. Router objects are attached to Activity/containing ViewGroup pairs. Routers do not directly render or push Views to the container ViewGroup, but instead defer this responsibility to the ControllerChangeHandler specified in a given transaction.
ControllerChangeHandler ControllerChangeHandlers are responsible for swapping the View for one Controller to the View of another. They can be useful for performing animations and transitions between Controllers. Several default ControllerChangeHandlers are included.
RouterTransaction Transactions are used to define data about adding Controllers. RouterTransactions are used to push a Controller to a Router with specified ControllerChangeHandlers, while ChildControllerTransactions are used to add child Controllers.

Getting Started

Minimal Activity implementation

class MainActivity : AppCompatActivity() {

    private lateinit var router: Router

    override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)

      setContentView(R.layout.activity_main)

      val container = findViewById<ViewGroup>(R.id.controller_container)

      router = Conductor.attachRouter(this, binding.controllerContainer, savedInstanceState)
        .setPopRootControllerMode(PopRootControllerMode.NEVER)
        .setOnBackPressedDispatcherEnabled(true)

      if (!router.hasRootController()) {
        router.setRoot(RouterTransaction.with(HomeController()))
      }
    }
}

Minimal Controller implementation

class HomeController : Controller() {

  override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup,
    savedViewState: Bundle?
  ): View {
    val view = inflater.inflate(R.layout.controller_home, container, false)
    view.findViewById<TextView>(R.id.tv_title).text = "Hello World"
    return view  
  }
}

Sample Project

Demo app - Shows how to use all basic and most advanced functions of Conductor.

Controller Lifecycle

The lifecycle of a Controller is significantly simpler to understand than that of a Fragment. A lifecycle diagram is shown below:

Controller Lifecycle

Advanced Topics

Retain View Modes

setRetainViewMode can be called on a Controller with one of two values: RELEASE_DETACH, which will release the Controller's view as soon as it is detached from the screen (saves memory), or RETAIN_DETACH, which will ensure that a Controller holds on to its view, even if it's not currently shown on the screen (good for views that are expensive to re-create).

Custom Change Handlers

ControllerChangeHandler can be subclassed in order to perform different functions when changing between two Controllers. Two convenience ControllerChangeHandler subclasses are included to cover most basic needs: AnimatorChangeHandler, which will use an Animator object to transition between two views, and TransitionChangeHandler, which will use Lollipop's Transition framework for transitioning between views.

Child Routers & Controllers

getChildRouter can be called on a Controller in order to get a nested Router into which child Controllers can be pushed. This enables creating advanced layouts, such as Master/Detail.

RxJava Lifecycle

If the AutoDispose dependency has been added, there is a ControllerScopeProvider available that can be used along with the standard AutoDispose library.

Community Projects

The community has provided several helpful modules to make developing apps with Conductor even easier. Here's a collection of helpful libraries:

License

Copyright 2020 BlueLine Labs, Inc.

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.

conductor's People

Contributors

3flex avatar adi1133 avatar blazeroni avatar burntcookie90 avatar chris-horner avatar dimsuz avatar dsteve595 avatar dtrabo avatar erickuck avatar fergusonm avatar hannesstruss avatar inorichi avatar jamesonwilliams avatar leonardo2204 avatar magillus avatar marionoll avatar matnazaroff avatar namolem avatar nomisrev avatar paulwoitaschek avatar pyricau avatar ravidsrk avatar shaishavgandhi avatar shchurov avatar sockeqwe avatar soniccat avatar stefma avatar tmtron avatar xxfast avatar yshrsmz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

conductor's Issues

NPE when I picked a image

When I touched "Target Controller", "PICK IMAGE (FROM GALLERY)", and picked a image,
then the demo app crashed.

 Process: com.bluelinelabs.conductor.demo, PID: 5946
 java.lang.RuntimeException: Failure delivering result ResultInfo{who=android:fragment:0, request=126, result=-1, data=Intent { dat=content://media/external/images/media/27659 }} to activity {com.bluelinelabs.conductor.demo/com.bluelinelabs.conductor.demo.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.ImageView.setImageURI(android.net.Uri)' on a null object reference
     at android.app.ActivityThread.deliverResults(ActivityThread.java:3542)
     at android.app.ActivityThread.handleSendResult(ActivityThread.java:3585)
     at android.app.ActivityThread.access$1300(ActivityThread.java:147)
     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1332)
     at android.os.Handler.dispatchMessage(Handler.java:102)
     at android.os.Looper.loop(Looper.java:135)
     at android.app.ActivityThread.main(ActivityThread.java:5237)
     at java.lang.reflect.Method.invoke(Native Method)
     at java.lang.reflect.Method.invoke(Method.java:372)
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906)
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:701)
  Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.ImageView.setImageURI(android.net.Uri)' on a null object reference
     at com.bluelinelabs.conductor.demo.controllers.TargetDisplayController.setImageView(TargetDisplayController.java:98)
     at com.bluelinelabs.conductor.demo.controllers.TargetDisplayController.onActivityResult(TargetDisplayController.java:68)
     at com.bluelinelabs.conductor.Router.onActivityResult(Router.java:52)
     at com.bluelinelabs.conductor.internal.LifecycleHandler.onActivityResult(LifecycleHandler.java:130)
     at android.app.Activity.dispatchActivityResult(Activity.java:6145)
     at android.app.ActivityThread.deliverResults(ActivityThread.java:3538)
     at android.app.ActivityThread.handleSendResult(ActivityThread.java:3585) 
     at android.app.ActivityThread.access$1300(ActivityThread.java:147) 
     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1332) 
     at android.os.Handler.dispatchMessage(Handler.java:102) 
     at android.os.Looper.loop(Looper.java:135) 
     at android.app.ActivityThread.main(ActivityThread.java:5237) 
     at java.lang.reflect.Method.invoke(Native Method) 
     at java.lang.reflect.Method.invoke(Method.java:372) 
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906) 
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:701) 

Device: Xiaomi2(MI 2S), Android 5.0.2 LRX22G.

on fast rotation (portrait/lansdcape), viewController crash

Hi
I start to using this lbrary, looking nice.
Now I have dashboard view with view pager which include recyclerView.
On rotation, i got crash (not always but very frequently ) in base Controller.java class.

Any ideas ?
Thanks!

04-26 17:34:41.439 28412-28412/com.app.app E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.app.app, PID: 28412
java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.View.findViewById(int)' on a null object reference
at com.bluelinelabs.conductor.Controller.attachChildController(Controller.java:689)
at com.bluelinelabs.conductor.Controller.attach(Controller.java:760)
at com.bluelinelabs.conductor.Controller.access$400(Controller.java:38)
at com.bluelinelabs.conductor.Controller$6.onViewAttachedToWindow(Controller.java:848)
at android.view.View.dispatchAttachedToWindow(View.java:15816)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3137)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3145)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3145)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3145)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3145)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3145)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3145)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1731)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1437)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7397)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:920)
at android.view.Choreographer.doCallbacks(Choreographer.java:695)
at android.view.Choreographer.doFrame(Choreographer.java:631)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:906)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7224)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

startActivityForResult alternative for Controllers?

Hi o/

For my app I decided to try to use just a single Activity, and have different "screens" (something like an Activity) using only controllers.

This works great. However, I'm not sure how to best simulate the startActivityForResult functionality using controllers alone.

Basically I want the previous controller on the "task stack" to get the results of the "top" controller.

Right now I'm using something like this to pass results from one controller to the another:

            val c = router.getControllerWithTag(MainActivity.TAG_MAIN_CONTROLLER)
            (c as ScreenMainController).routeFromOthersScreens = MainMvpView.Route.Steps
            router.popCurrentController()

This has the disadvantage of adding hard dependencies between the controllers, and they need to know how to relate between them.

My question is if there is anything similar to onActivityResult but for controllers?

Thank you :)

Add onActivityLowMemory

Some time we need to listen to onLowMemory callback in our Controller.
For example when I'm working with Mapbox, it requires to call onLowMemory:

 @Override
    public void onLowMemory() {
        super.onLowMemory();
        mapView.onLowMemory();
    }

If you ok with this feature, I'm happy to do a PR.

Lifecycle Documentation

Hi,

I'm trying to understand the lifecycle of a Controller and I have a doubt:

How can I differentiate a configuration change such as the device rotation from a navigation event such as the Controller being popped or be sent to the backstack?

When are the onSaveInstaceState and onSaveViewState methods called on each case?

I think more documentation on that topic would be great to fully understand why the lifecycle of a Controller is easier than the Activity one, as you state.

Thank you very much.
Your lib is great :)

Lifecycle question about getTargetController()

What lifecycle callback should I use to call getTargetController() as soon as possible ?

The call works from inside onCreateView(), but fails inside onRestoreInstanceState() or any of the constructors.
Ideally I'd use a lifecycle callback that happens only once per instance but has access to getTargetController()

My usecase is grabbing data from a "parent" controller, I set the parent controller using setTargetController(), later I want the child to get a dagger component from the parent to inject itself.

Am I doing it completely wrong ?

@NotPersisent Annotation

Shouldn't be nice to have a "@NotPersistent" annotation, so when moving to one Controller to another, maybe you don't want to save the "backstack" Controller.
For example: You have a screen with people list, you add a new person, close the current insertion Controller and go back, the list will be saved at the previous position (when the user clicked the '+' button).

target controller as constructor parameter

I'm not 100% sure, but I think that the current way Controller.setTargetController() could cause problems because one could change the target controller afterwards. I think it would be better to set the target controller as constructor parameter to avoid such issues.

ControllerPagerAdapter does not save or restore view state

Hello,

Firstly, Thanks a lot for this great library!

I'm using ControllerPagerAdapter inside a ViewPager, and each child controller contains a RecyclerView.
The problem is that my RecyclerView displays a list of 50 items of size and I'd like to save the 50 items in a bundle so that I could prevent loading it from the backend.

The problem arises when I scroll the ViewPager and the adapter's destroyItem method is called, you destroy the Child controller completely and remove it from the host controller as a result neither onSaveViewState or onRestoreViewState is called and I'm unable to restore the removed page state when the user returns to it and everything is loaded all over again.

Perhaps restoreState and saveState should be overriden to fix this behavior?

Hope that I've been able to explain the issue clearly.

Thanks!

Integration with Facebook SDK login

Facebook SDK login requires us to provide a reference to an activity or fragment so it will call startActivityForResult() internally. It means that we need to pass LifecycleHandler fragment reference there in order to get the result to its onActivityResult() and dispatch it to controllers. However, I didn't find an easy way to get that reference. Is there any official workaround?

What's the idiomatic way to wait for layout?

I'm working on an app that displays data in a RecyclerView using a GridLayoutManager. I'm trying to compute the spanCount (the number of columns) by dividing the Controller's View's width by a desired tile width, but unfortunately view.width is 0 even in onAttach(View). Is this intended behaviour? Is there an idiomatic way to deal with waiting for the initial layout apart from setting a listener in the View's ViewTreeObserver? To be clear, the View's width is set to match_parent.

minSdkVersion from 16 to 15

Please reduce the minSdkLevel to 15 (Android 4.0.X): I've an app in production with minimal sdk level to 15 and I want to use this framework. Thanks

getCurrentController() possibility?

Any chance you can expose a method for getting the current controller? Or maybe one already exists and I've missed it?

I’m using conductor for a ‘wizard type’ flow and want to simplify things by doing something like: getCurrentController().onNext() OR getCurrentController().onPrevious, etc. I noticed that I can peek at the backstack, but it’s a private package so I can’t get a reference to it.

Thoughts or possibilities?
Thanks for the awesome work.

[Discussion] Controller constructor validation and Bundle helper

Controllers that call super(bundle) directly don't pass validation. This makes the API more verbose. I know it is not good to try and find constructors in super types but we could have an initialization flag to know if those were called.

Also, there could be a generic Controller builder that would substitute the BundleBuilder helper. This could ease a bit the API. Something like:

Controller.newBuilder(AController.class).putInt(KEY_SMTG, value).putString(KEY_SMTG_ELSE, anotherValue).pushChangeHandler(new DifferentChangeHandler()).toRouterTransaction();

If the idea is somewhat valid I can send a WIP PR to better show the interface.

Sonatype Authentication

As discussed in #42 here is just a friendly reminder how to setup authentication for snapshot deployment from travis properly (remove previous env / global / secret definitions:

travis encrypt -r bluelinelabs/Conductor "ORG_GRADLE_PROJECT_NEXUS_USERNAME=InsertYourUsername" --add
travis encrypt -r bluelinelabs/Conductor "ORG_GRADLE_PROJECT_NEXUS_PASSWORD=InsertYourPassword" --add

Trouble with onActivityResult

I am having trouble with the onActivityResult callback in my controller. Before 1.1.0 it was running fine, but after it, when it gets called back, my views' references are null.

I've noticed my onDestroyView gets called before the callback gets called. A workaround is to make my Controller RETAIN_DETACH, but not sure this is what I wanted in the first place.

I am using the speech to text API through intents. So, I guess that to reproduce the issue you can simply:

final Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "en");
startActivityForResult(intent, REQ_CODE_SPEECH_INPUT); // any constant value you want

My callback is like:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {

    if (requestCode == REQ_CODE_SPEECH_INPUT
            && resultCode == Activity.RESULT_OK
            && data != null) {

        final ArrayList<String> result = data
                .getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);

        // Just displaying the results
        spokenText.setText(spokenText.getText().toString() + " " + result.get(0));
    }
}

Any help is appreciated :)

Is there a reason for the inflate/bind view creation method distinction?

I would like to convince you to combine them!

  • They are always called in succession and have the same collective responsibility: going from no view to usable views.
  • The inflate/bind split is usually seen in places where views are re-used and thus re-binding needs to occur without a respective inflate. This is never true for a controller since the mapping is 1:1.
  • The split isn't conducive to databinding or other mechanisms where inflating and binding are a single step.
  • You can still achieve the inflate/bind split at the application layer with a custom base class that marks the single inflate method final and delegates to two abstract methods for each responsibility. And potentially it only delegates to one method if you are using ButterKnife for all binding in that application-level base class.

Production Ready ?

First of all, excellent job, another excellent library to "bypass" the Fragments nightmare !
I have some doubts regarding this lib:

  1. Is it production ready ? I mean, can I start an app based on this lib to go to prod ?
  2. How can I reference the Activity/Fragment ? e.g: To login using the Facebook SDK you need to pass the activity or fragment reference, not a Context.
  3. What is the difference between using this lib or Flow ?
  4. Does the lib support out-of-the-box saving/restoring view states ? e.g. navigating from one "screen" to another and going back, everything will be as it was left off ? (even if app gets killed by Android ?)

Sorry to bother and thanks again!

[Feature] Nested backstacks

Hi!

Is there any way to push a [child] controller from within another child controller?

Let's say I'm displaying a child controller (named A) in a container view (named V), that it is just a part of the whole screen.

What I want is to push another controller from A and make its view be contained in V.

From what I understand, if we push a controller using the router, that would replace the view of the parent controller, and that's not what I want to achieve.

Thank you in advance!

EDIT

My use case is:

  • I want a tabbed controller so I'm using ControllerPagerAdapter.
  • I want its child controllers to have their own backstacks so I can navigate within that "section" and keep the tabbar always visible.

LifecycleListener: postCreateView() called after onRestoreViewState()

Hi,
I have noticed that LifecycleListener.postCreateView() is called after LifecycleListener.onRestoreViewState() .

Is that per purpose? To me it sounds like a little bit a contradiction of Conductors lifecycle diagram where onCreateView() becomes before onRestoreViewState() so that I was expecting that LifecycleListener.postCreateView() will be called right after onCreateView() and before onRestoreViewState().

I'm facing the problem where I do some View foo = findViewById(R.id.foo) in LifecycleListener.postCreateView() but also wanted to recover the state of View foo manually in LifecycleListener.onRestoreViewState() which leads to a NullPointerException because LifecycleListener.postCreateView() is called after LifecycleListener.onRestoreViewState() ...

Obviously I can rewrite my code to avoid that problem, I just wanted to ask if this execution order is by design?

Add getString() method to Controller

Thoughts on adding a getString method to the Controller class? This convenience is offered by both Activity and Fragment in the Android framework and prevents having to reference Resources first.

I'd be happy to make this contribution if it gets the go ahead.

Note: there is also a second, overloaded method for handling string formatting as well.

Expose backstack

Would it make sense to expose the Backstack through Router, e.g. as Iterable<RouterTransaction>?

I'm working on a separate utility that allows binding resources to controller lifecycles and makes them available for child controllers and controllers later in the backstack. (Think: shared state of a multi-step wizard or a Dagger component). In there, I'd like to have something like findResource(someChildController, "MyResource") that iterates back up to the parent controller and backwards through the backstack.

Backstacks obtained this way could also be built upon and used for updating history, as requested in #52.

Happy to implement this!

Edit: grammar

minsdk 16

The project minsdk is set to 16. Is this a hard requirement of the library ?

Calling Activity.recreate() results in an empty view

I've modified the demo's NavigationDemoController to call getRouter().getActivity().recreate() when the up button is pressed. The Activity will be destroyed and created again, but the view is blank. Pressing back once will correctly show the view of the previous Controller in the backstack.

Annotation process to generate controller builders

Picking up the discussion started with @sockeqwe on #3 here. I've done some work with annotation processors in the past (LoganSquare), but your FragmentArgs lib is much closer to what this would take than anything I've ever done. Any suggestions for proceeding?

I'm sure you've already put a lot of thought into this for FragmentArgs, but has anyone thought of a way to skip the step of having to build the project before the builder is generated? With LoganSquare I was able to abstract the generated code away enough that developers didn't need to care about whether or not the generated code was actually there or not, but my use case was quite a bit different. I have a feeling there probably isn't a way to abstract that away when the builder itself is what's being generated.

RxLifecycle 1.1.5 snapshot is currently broken

https://oss.sonatype.org/content/repositories/snapshots/com/bluelinelabs/conductor-rxlifecycle/1.1.5-SNAPSHOT/conductor-rxlifecycle-1.1.5-20160419.202538-1.pom

There is a dependency on Conductor:conductor:unspecified, this dependency is broken.

<dependency>
<groupId>Conductor</groupId>
<artifactId>conductor</artifactId>
<version>unspecified</version>
<scope>compile</scope>
</dependency>

The workaround I am using is including the dependency using:

compile 'com.bluelinelabs:conductor:1.1.5-SNAPSHOT'
compile('com.bluelinelabs:conductor-rxlifecycle:1.1.5-SNAPSHOT') {
    exclude group: 'Conductor', module: 'conductor'
}

Deep linking and synthesizing the back stack

I want to be able to deep-link into a part of my app. As an example, an email app will want to go directly to an email from a notification. According to https://developer.android.com/training/implementing-navigation/temporal.html#SynthesizeBackStack (cached version since the docs seem to be down), the correct behavior is to add to the back stack so that it's as though the user navigated there manually. Essentially, I want to be able to add multiple Controllers to the back stack in one transaction. I could do a bunch of pushController calls in one go, but that seems wasteful since (if I understand the implementation correctly) each controller will have to create its View.

Flow solves this by using "key" objects to reference individual parts of the back stack. Basically immutable objects. It's then possible to create a History object, which is essentially a list of keys, and use that as the new backstack. Could we have something similar, maybe a Router.setBackStack(List<Controller>, ControllerChangeHandler) method?

Add an onCreate() method

Currently developers are expected to do controller initialization in the constructor, which is great for some stuff, but makes other things a lot harder. Controllers don't have a reference to a router, activity, or parent controllers in the constructor, so things like dagger injection become more tricky. If you require references to these things for your initialization, you have to wait for the onCreateView() callback, then make sure you only run that code block once. Awkward.

An onCreate() method would be the callback signaling that the controller is fully configured (minus the view) and has all the references it'll need.

onCreateOptionMenu not called

When pushing a new controller, onCreateOptionsMenu is not being called on the pushed controller.

If i were to use the tested controller as the root controller on an activity, onCreateOptionsMenu is called as expected.

popCurrentController ConcurrentException

My scenario is:
I have 2 Controllers, a list and add button that goes to another Controller (edit or new). The second controller has a Toolbar with a back button (actionBar.setDisplayHomeAsUpEnabled(true)).
My callback:

@Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                getRouter().popCurrentController();
            default:
                return super.onOptionsItemSelected(item);
        }
    }

The add button (1st Controller):
getRouter().pushController(RouterTransaction.builder(new KidsRegisterController(false, null, this)).build());

The problem is, after the second open and go back (From 1st -> 2nd and then back for the second time)
the second Controller is never popped, and if I click the back button I get this exception:

FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to destroy activity {br.com.dinda.qa/br.com.dinda.views.activities.KidsActivity}: java.util.ConcurrentModificationException
at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3273)
at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3291)
at android.app.ActivityThread.access$1200(ActivityThread.java:130)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1248)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4745)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.util.ConcurrentModificationException
at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:569)
at com.bluelinelabs.conductor.Router.onActivityDestroyed(Router.java:392)
at com.bluelinelabs.conductor.internal.LifecycleHandler.onDestroy(LifecycleHandler.java:123)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:983)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1035)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1017)
at android.app.FragmentManagerImpl.dispatchDestroy(FragmentManager.java:1826)
at android.app.Activity.performDestroy(Activity.java:5171)
at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1109)
at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3260)
at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3291) 
at android.app.ActivityThread.access$1200(ActivityThread.java:130) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1248) 
at android.os.Handler.dispatchMessage(Handler.java:99) 
at android.os.Looper.loop(Looper.java:137) 
at android.app.ActivityThread.main(ActivityThread.java:4745) 
at java.lang.reflect.Method.invokeNative(Native Method) 
at java.lang.reflect.Method.invoke(Method.java:511) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553) 
at dalvik.system.NativeStart.main(Native Method) 

Calling setRoot() in onNewIntent() doesn't remove old view

I've added the following to the MainActivity of the demo:

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    mRouter.setRoot(new HomeController());
}

When you use home and then launch the app again from the launcher onNewIntent() will be called. After scrolling a bit you'll notice that the home list exists two times.

In setRoot() the call to currentTop.controller.getView() will return the old View and that View won't be removed from the ViewGroup. The trackDestroyingControllers(mBackStack.popAll()); call seems to remove the View reference from the old controller which means that the following Controller change will not remove the old View from the ViewGroup.

Multiple routers

Is it possible to have 2 activities, each with their own router?

Using MVP

I am wanting to switch from Flow to Conductor, but I cannot seem to grasp my head around when to create my presenter in the controller. After looking at the source, I would imagine that creating the presenter in preAttach then attaching the view to the presenter and detaching the view in postDetach would be a good route to go, but I am struggling on actually doing that. Any help/advice would be greatly appreciated!

(If this isn't the right place for this, let me know and I'll remove it)

Is there a reason for the save/restore and bind/unbind distinction?

I would like to convince you to combine them!

Both pairs are really responsible for the same thing: moving the controller from a place with no view to a place with a fully usable view. In the happy case where views are saving all the state themselves, nothing changes about your implementation. And if you do need custom storage around view state it now happens at the same time and in the same place where you're already setting them up or tearing them down.

Bonus: greatly simplified lifecycle (especially combined with #8) which is focused around the larger what not the how things happen. This makes you opinionated about the lifecycle instead of the implementation of the lifecycle.

A simplified lifecycle also reduces the question of when methods are called by putting them into higher-level categories. The application-layer is allowed to decide what distinction they want to break apart based on their library and conventions. Unlike the mistakes Android made in Activity and Fragment, you don't need to cater to subclasses trying to incorrectly mix-in functionality through inheritance instead of composition via delegation. When the lifecycle is simplified you even make delegation easier since there's less for libraries wanting to hook into the lifecycle to require hooks for.

What's also nice about the new lifecycle (assuming #8 as well) is that it's only ever one step to move from state to state, not multiple. This eliminates the question of conditional callback uncertainty and ordering uncertainty:

  • Birth? One callback (maybe): onRestoreInstanceState.
  • Setting up a view? One callback: initializeView (or whatever).
  • Displaying view? One callback: onAttach.
  • View no longer displaying? One callback: onDetach.
  • View being recycled? One callback: destroyView.
  • Deathbed? One callback: onSaveInstanceState.

Router.handleBack() returns false when root controller is popped

When root controller is being popped in handleBack() and controller does not handle back press, popCurrentController() returns false because backstack became empty after popping happened and thus handleBack() returns false. It means that router did not handle back press when it actually did by doing pop.

It works well when root controller is on root view within activity. But in my case I have root view without any Router attached and its child View with backstack controlled by Router:

+----------------------------------------+
|                  +-------------------+ |
|                  |                   | |
| Root View        | Child View with   | |
| that controls    | attached Router   | |
| its child        | and backstack     | |
| (without own     |                   | |
| backstack)       |                   | |
|                  |                   | |
|                  |                   | |
|                  |                   | |
|                  +-------------------+ |
+----------------------------------------+

So when back pressed on activity I expect it to be consumed by child View's Router even if only root controller left in backstack.

Btw thank you for the library!

README shows wrong usage of Controller API

In a Controller, the only method we need to override is:

    @NonNull
    @Override
    protected View inflateView(LayoutInflater inflater, ViewGroup container) {
        return null;
    }

But README shows:

    @Override
    protected int layoutId() {
        return R.layout.controller_overlay;
    }

    @Override
    public void onBindView(@NonNull View view) {
        super.onBindView(view);

        ((TextView)view.findViewById(R.id.tv_title)).setText("Hello World");
    }

Facebook integration

I'm implementing a Facebook login implementation on top of the demo app, but I can't find a way to bind the onActivitityResult from Facebook to my Controller.
What I have:

/**
 * Created by leonardo on 4/11/16.
 */
public class FacebookController extends BaseController {

    @Bind(R.id.login_button)
    LoginButton loginButton;

    private CallbackManager callbackManager;

    @Override
    protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
        return inflater.inflate(R.layout.controller_facebook, container, false);
    }

    @Override
    protected void onViewBound(@NonNull View view) {
        super.onViewBound(view);

        callbackManager = CallbackManager.Factory.create();
        loginButton.registerCallback(callbackManager, new FacebookCallback<LoginResult>() {
            @Override
            public void onSuccess(LoginResult loginResult) {
                Toast.makeText(getActivity(), "Logged in !", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onCancel() {
                Toast.makeText(getActivity(), "Canceled by the user!", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onError(FacebookException error) {
                Toast.makeText(getActivity(), "Error:"+ error.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });
        loginButton.setOnClickListener(null);
    }

    @OnClick(R.id.login_button)
    public void onClick(View v){
        LoginManager.getInstance().logInWithReadPermissions(getActivity(), null);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        callbackManager.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    protected String getTitle() {
        return "Facebook SDK Login";
    }
}

I realized that in order to get the Activity's lifecycle callbacks I must call the Controller startActivityForResult for instance, but as I'm using Facebook SDK I don't have the control over this.
How should I proceed in that case ?

Thanks

Child Controller options menu?

Hi,

Using setHasOptionsMenu() in a child controller from a ControllerPagerAdapter doesn't seem to call any of the options menu-related callbacks. Is this intentional? Love the library!

Thanks!

It seems RETAIN_DETACH doesn't work as expected

So. I expect retained view to be reattached after activity recreation. But nothings happens.

Controller keeps view — thats ok. But view itself keeps its parent. And SimpleSwapChangeHandler can't add it on router recreation:

if (to != null && to.getParent() == null) {
    container.addView(to);
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.