-
Notifications
You must be signed in to change notification settings - Fork 72
Migrating from Magellan 1.x
Welcome to Magellan! We're working hard on an update; this is the work-in-progress documentation for that update. For the old documentation, see our old wiki page, Magellan 1.x Home. If you have questions/comments/suggestions for the new documentation, please submit an issue and we'll explain ourselves better.
We put in a lot of work to make Screen
s work almost exactly the same as before. The few breaking changes can be found below, but for the most part, most Screen
logic will work just fine.
Coming soon!
You cannot use Screens with Journey
navigators - you must use Magellan 2.x base classes instead. This means you'll likely need to migrate existing Screen
s accordingly when linking 1.x and 2.x code together.
The biggest difference between Magellan 1.x's Screens and 2.x's Step
s is that Screen
classes are bound to a corresponding ScreenView
by the framework. In contrast, Step
s occupy a 'vertical slice' your application - by default implementing both business and view logic. Given this, there are a few ways to transform an existing Screen
into Magellan 2.x paradigms:
The first, and most obvious, is to directly convert the Screen
/ScreenView
pair into a Step
: 1.) merge the view logic found in your ScreenView with your Screen and 2.) replace inline view inflation with View Binding.
For a more flexible approach, change your existing Screens
to extend LegacyStep
as necessary. This allows you to separate Controllers and Views using familiar 1.x paradigms, while still extending 2.x's base classes.
While adopting Magellan 2.x, you will most likely have a period when some of your codebase is still using Magellan 1.x paradigms. Much of this can be rectified by creating Journey
s for new UI, and using LegacyStep
as described above.
However, sometimes this isn't feasible, so we must do something to rectify the very different navigation models of 1.x and 2.x. If you're unfamiliar with Magellan 2.x's navigation paradigm, see Thinking in Magellan. To summarize: Magellan 1.x's backstack has a flat, linked-list-like structure, where each screen is responsible for navigating to the next screen; Magellan 2.x has a tree-like, parent-focused structure, where parent Journey
s are responsible for navigating between their children.
To rectify these, attach a LegacyExpedition
to your Activity. This serves as a root Journey
with a 1.x style backstack, allowing navigational interop at the top level.
Recall that in idiomatic Magellan 2.x, navigation logic should be confined to navigator-owning objects. Therefore, while each Screen has a navigator reference, it's now considered a bad practice to use it directly. We'll need some creativity to properly scope our 1.x -> 2.x navigation operations.
While your legacy code will lack the tree-like navigational structure of 2.x described above, you can think of your existing navigation graph as living under a 'root' Journey (see LegacyExpedition above). Playing this idea forward, we can achieve 2.x paradigms simply by placing any new navigation logic inside our root Journey class, and then injecting this Journey into Screens which require this navigation logic.
class MyExpedition : LegacyExpedition(...) {
fun goTo2x() {
navigator.goTo(Magellan2xJourney())
}
}
class Magellan1xScreen : Screen<...>(...) {
@Inject lateinit var expedition: MyExpedition
fun goTo2x(): {
expedition.goTo2x()
}
}
Of course at scale, this root Journey class will become packed with many navigation operations. You can solve this by factoring the Journey's navigation logic into smaller, injectable 'router' classes that rely on the Expedition
’s Navigator
to navigate between pages.
Warning: avoid injecting these routers into Magellan 2.x components (
Journey
s orStep
s), as this would allow children to navigate away from their parents without the parents' knowledge. While this is not technically wrong, it breaks navigational encapsulation, making it much harder to reason about. This is a similar idea to why all coroutines must live in aCoroutineScope
.
Navigating from 2.x to 1.x requires less creativity, as there is only one way to do it without breaking encapsulation: pass a navigational lambda down from the Expedition
.
While it can be painful if you have many layers between the Expedition
and the Journey requesting this navigation, this is actually the same pattern as you would use for any top-level (Expedition
-level) navigation. It honors the navigational encapsulation that Journey
s provide, i.e. that child must perform navigation operations through their parent.
class MyExpedition : LegacyExpedition(...) {
fun goTo1x() {
navigator.goTo(Magellan1xScreen())
}
fun goTo2x() {
navigator.goTo(Magellan2xJourney { goTo1x() })
}
}
class Magellan2xJourney(
val goToMagellan1x: () -> Unit
) : Journey<...>(...) {
fun onCreate(): {
navigator.goTo(MyStep(goToMagellan1x))
}
}
class MyStep(
val goToMagellan1x: () -> Unit
) : Step<...>(...) {
fun onSomethingClicked() {
goToMagellan1x()
}
}
Made with 💚 by the Wealthfront Android Team.