Migrate from V2 to V3
Ballast v3.0.0 is a major release, which some breaking changes in its public API and significant improvements to its internals. Most of these changes were introduced in a way that is source backward-compatible, but no attempt was made to maintain strict binary backward-compatibility. Most projects that were compiled with Ballast v2.3 should be able to be updated to Ballast v3.0 and still compile, though a library that depends on Ballast v2.3 may need to be recompiled against Ballast v3 to work properly.
The breaking changes are intended to be easily adopted, with deprecations introduced from v2.3 and automatic fixes available. Features that were already marked as deprecated in v2.3 have been removed.
See below for the complete list of changes, and how to update your project to support these changes.
Dependency artifact changes:
ballast-firebase-analyticsandballast-firebase-crashlyticsmodules are reimplemented for all platforms as basic reportingballast-firebase-analyticsimplementation (without Firebase dependency) is now inballast-analytics.ballast-firebase-analyticsdepends onballast-analyticsand addsFirebaseAnalyticsTracker.FirebaseAnalyticsInterceptoris now deprecatedballast-firebase-crashlyticsimplementation (without Firebase dependency) is now inballast-crash-reporting.ballast-firebase-crashlyticsdepends onballast-crash-reportingand addsFirebaseCrashReporter.FirebaseCrashlyticsInterceptoris now deprecated
ballast-corehas been broken out into several smaller artifacts:ballast-api,ballast-logging,ballast-viewmodel, andballast-utils.ballast-coredepends on all these new artifacts, so no changes are necessary unless you wish to pick which of these core modules you need.
Breaking Changes
- Changes to
BallastNotificationBallastNotificationpreviously had a hard reference to theBallastViewModelwhich emitted the notification, which led to potential issues with memory leaks or incorrect usage within an Interceptor. The intended use-case for this property was to give access to the name of the VM for debugging purposes, soBallastNotification.vmhas been replaced withBallastNotification.viewModelTypeandBallastNotification.viewModelName
nameandtypeproperties are no longer exposed through theBallastViewModelinterface. These were intended to be used by Interceptors for debugging, but because of the changes toBallastNotification, are no longer necessary as part of theBallastViewModelpublic API.- A new subclass of
Queuedhas been added:Queued.CloseGracefully. fetchWithCache()can now be used from any ViewModel, rather than being restricted toBallastRepository. This was done by adding a newEventstype parameter, so it now takes 3 type paramters rather than 2Cachedcan now be used with nullable values, by removing the bounds of its type parameter- Drops support for deprecated KMPP targets: JS Legacy, iosArm32
New APIs
InputHandlerScope.cancelSideJob()allows you to cancel a running sideJob, rather than needing to create an empty one to cancel it.SideJobScope.keygives you thekeyused to start the sideJobSideJobScope.getInterceptor()allow you to find an Interceptor that is registered within the ViewModel, for using its public members (such asBallastUndoInterceptor.undo()orKillSwitch.requestGracefulShutdown())KillSwitchinterceptor, to request graceful shutdownRestoreStateScope.postInput()andRestoreStateScope.postEvent()added as a replacement ofSavedStateAdapter.onRestoreComplete, allowing more flexibility when restoring the state of a ViewModel.AndroidViewModelandAndroidBallastRepositorynow accept an optionalcoroutineScopewhich will be passed into theViewModelconstructor. If this coroutineScope implementsCloseable, it will be cancelled when the ViewModel is cleared. If it does not implementCloseable, it will be wrapped in aCloseable { }block to cancel it instead.- New platform-specific loggers are available:
- You can now intelligently serialize the data sent to the Debugger UI, for example by serializing it to JSON.
BallastDebuggerInterceptornow has 3 optional properties forserializeInput,serializeEvent, andserializeStatewhich you could use to convert those values into JSON or any other serialized format for improved display in the Intellij Plugin's Debugger UI. By default, values are serialized by calling their.toString(), which was the previous the default behavior.
Deprecations
Several features have been deprecated with the release of v3.0.0, which will be removed in Ballast v4. Most of these changes are simple name changes, or related to the overhaul of ViewModel internals.
FirebaseAnalyticsInterceptor
FirebaseAnalyticsInterceptor is deprecated. AnalyticsInterceptor is the intended replacement, which is now
available in all supported targets. In addition to supporting trackers other than Firebase, it also allows you more
flexibility in selecting which Inputs to track, so that you can now track Inputs without needing the
@FirebaseAnalyticsTrackInput annotation.
// Old usage
BallastViewModelConfiguration.Builder()
.apply {
this += FirebaseAnalyticsInterceptor(Firebase.analytics) // FirebaseAnalyticsInterceptor class
}
.build()
// New usage
BallastViewModelConfiguration.Builder()
.apply {
// customized interceptor
this += AnalyticsInterceptor(
tracker = FirebaseAnalyticsTracker(Firebase.analytics),
shouldTrackInput = { it.isAnnotatedWith<FirebaseAnalyticsTrackInput>() },
)
// helper function for setting up tracking with Firebase
this += FirebaseAnalyticsInterceptor() // FirebaseAnalyticsInterceptor factory function, which returns AnalyticsInterceptor
}
.build()
FirebaseCrashlyticsInterceptor
FirebaseCrashlyticsInterceptor is deprecated. CrashReportingInterceptor is the intended replacement, which is now
available in all supported targets. In addition to supporting crash reporters other than Firebase, it also allows you
more flexibility in selecting which Inputs to ignore, so that you can now ignore Inputs without needing the
@FirebaseCrashlyticsIgnore annotation.
// Old usage
BallastViewModelConfiguration.Builder()
.apply {
this += FirebaseCrashlyticsInterceptor(Firebase.crashlytics) // FirebaseCrashlyticsInterceptor class
}
.build()
// New usage
BallastViewModelConfiguration.Builder()
.apply {
// customized interceptor
this += CrashReportingInterceptor(
tracker = FirebaseCrashReporter(Firebase.crashlytics),
shouldTrackInput = { !it.isAnnotatedWith<FirebaseCrashlyticsIgnore>() },
)
// helper function for setting up crash reporting with Firebase
this += FirebaseCrashlyticsInterceptor() // FirebaseCrashlyticsInterceptor factory function, which returns CrashReportingInterceptor
}
.build()
SavedStateAdapter
SavedStateAdapter.onRestoreComplete() was previously used to send an Input after a ViewModel's state had been
restored. This function is now deprecated, and new methods added to RestoreStateScope (the receiver of
SavedStateAdapter.restore()) which accomplish the same functionality.
// Old usage
class ExampleSavedStateAdapter(
private val database: ExampleDatabase,
) : SavedStateAdapter<
ExampleContract.Inputs,
ExampleContract.Events,
ExampleContract.State> {
override suspend fun SaveStateScope<
ExampleContract.Inputs,
ExampleContract.Events,
ExampleContract.State>.save() {
saveDiff({ values }) { values ->
database.saveValues(values)
}
}
override suspend fun RestoreStateScope<
ExampleContract.Inputs,
ExampleContract.Events,
ExampleContract.State>.restore(): ExampleContract.State {
return ExampleContract.State(
values = database.selectValues(values)
)
}
override suspend fun onRestoreComplete(restoredState: State): Inputs? {
return ExampleContract.Inputs.Initialize
}
}
// New usage
class ExampleSavedStateAdapter(
private val database: ExampleDatabase,
) : SavedStateAdapter<
ExampleContract.Inputs,
ExampleContract.Events,
ExampleContract.State> {
override suspend fun SaveStateScope<
ExampleContract.Inputs,
ExampleContract.Events,
ExampleContract.State>.save() {
saveDiff({ values }) { values ->
database.saveValues(values)
}
}
override suspend fun RestoreStateScope<
ExampleContract.Inputs,
ExampleContract.Events,
ExampleContract.State>.restore(): ExampleContract.State {
val restoredState = ExampleContract.State(
values = database.selectValues(values)
)
postInput(ExampleContract.Inputs.Initialize)
return restoredState
}
}
SideJobScope
SideJobScope.currentStateWhenStarted is now deprecated, with no direct replacement. This property could lead to race
conditions if the state was changed between the time the sideJob was dispatched and when it was actually started, which
reduces the ability to know exactly what's running within the sideJob block.
Instead, capture a snapshot of the state yourself from the InputHandlerScope and pass that object into the sideJob.
// Old usage
sideJob("key") {
doSomethingWithState(currentStateWhenStarted)
}
// New usage
val currentState = getCurrentState()
sideJob("key") {
doSomethingWithState(currentState)
}
BallastInterceptor
BallastInterceptor has had 2 callbacks for its usage: BallastInterceptor.onNotify() to process Notifications one at
a time for simple reactive usage, and BallastInterceptor.start() for full access to the Flow<BallastNotification>
for more advanced usage. BallastInterceptor.onNotify() has been deprecated, so there should only be the single
entry-point for making new Interceptors.
// Old Usage
class CustomInterceptor<Inputs : Any, Events : Any, State : Any> : BallastInterceptor<Inputs, Events, State> {
override suspend fun onNotify(logger: BallastLogger, notification: BallastNotification<Inputs, Events, State>) {
// do something
}
}
// New Usage
class CustomInterceptor<Inputs : Any, Events : Any, State : Any> : BallastInterceptor<Inputs, Events, State> {
override fun BallastInterceptorScope<Inputs, Events, State>.start(
notifications: Flow<BallastNotification<Inputs, Events, State>>,
) {
launch(start = CoroutineStart.UNDISPATCHED) {
notifications.awaitViewModelStart()
notifications
.onEach {
// do something
}
.collect()
}
}
}
JsConsoleBallastLogger
JsConsoleBallastLogger is deprecated, to be replaced with JsConsoleLogger. This is just a name change, functionality
is identical.
AndroidBallastLogger
AndroidBallastLogger is deprecated, to be replaced with AndroidLogger. This is just a name change, functionality
is identical.
DefaultUndoController
DefaultUndoController is deprecated, to be replaced with StateBasedUndoController. This is in preparation to
maintain name-parity with a future feature to add an InputBasedUndoController. The StateBasedUndoController is also
itself built as a BallastViewModel, so you can add Interceptors to this UndoController if you need more advanced
logging or other features to better manage your undo/redo functionality.