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-analytics
andballast-firebase-crashlytics
modules are reimplemented for all platforms as basic reportingballast-firebase-analytics
implementation (without Firebase dependency) is now inballast-analytics
.ballast-firebase-analytics
depends onballast-analytics
and addsFirebaseAnalyticsTracker
.FirebaseAnalyticsInterceptor
is now deprecatedballast-firebase-crashlytics
implementation (without Firebase dependency) is now inballast-crash-reporting
.ballast-firebase-crashlytics
depends onballast-crash-reporting
and addsFirebaseCrashReporter
.FirebaseCrashlyticsInterceptor
is now deprecated
ballast-core
has been broken out into several smaller artifacts:ballast-api
,ballast-logging
,ballast-viewmodel
, andballast-utils
.ballast-core
depends 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
BallastNotification
BallastNotification
previously had a hard reference to theBallastViewModel
which 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.vm
has been replaced withBallastNotification.viewModelType
andBallastNotification.viewModelName
name
andtype
properties are no longer exposed through theBallastViewModel
interface. These were intended to be used by Interceptors for debugging, but because of the changes toBallastNotification
, are no longer necessary as part of theBallastViewModel
public API.- A new subclass of
Queued
has been added:Queued.CloseGracefully
. fetchWithCache()
can now be used from any ViewModel, rather than being restricted toBallastRepository
. This was done by adding a newEvents
type parameter, so it now takes 3 type paramters rather than 2Cached
can 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.key
gives you thekey
used 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()
)KillSwitch
interceptor, to request graceful shutdownRestoreStateScope.postInput()
andRestoreStateScope.postEvent()
added as a replacement ofSavedStateAdapter.onRestoreComplete
, allowing more flexibility when restoring the state of a ViewModel.AndroidViewModel
andAndroidBallastRepository
now accept an optionalcoroutineScope
which will be passed into theViewModel
constructor. 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.
BallastDebuggerInterceptor
now has 3 optional properties forserializeInput
,serializeEvent
, andserializeState
which 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.