Twenty-second release of RainbowCake.
- Added a
removeObserver
method toLiveDataCollection
- Kotlin 1.5.31
Twenty-first release of RainbowCake.
Using Dagger Hilt? RainbowCake now has you covered! Huge thanks to stewe93 for contributing this.
-
Include the new Hilt artifact:
implementation "co.zsmb:rainbow-cake-hilt:$rainbow_cake_version"
-
Set up Hilt following the official Android guide.
-
Call into the
getViewModelFromFactory
function of theco.zsmb.rainbowcake.hilt
package in your Activities or Fragments.
Check out the hilt-demo
module for an example of a basic setup.
Note: You may also use multiple DI solutions within the same RainbowCake project simultaneously, for example if you're migrating a project piece by piece. Even shared ViewModels will work!
There is now a Blank Hilt starter project available showcasing a blank app set up with RainbowCake's Hilt support.
- Android Gradle Plugin 7.0.1
- Kotlin 1.5.30
- Coroutines 1.5.1
- AppCompat 1.3.1
- ConstraintLayout 2.1.0
- Material 1.4.0
- Lifecycle 2.3.1
- Dagger 2.38.1
- Koin 3.1.2
- Timber 5.0.0
Twentieth release of RainbowCake.
Instead of providing its own extensions for requiring values from Bundles, RainbowCake now depends on requireKTX to handle these operations. The require
style methods in RainbowCake are now deprecated and will be removed.
Migrated the Koin integration module from 2.x to 3.x, including the change of dependency coordinates from org.koin
to io.insert-koin
. For more about the 3.x version of Koin, see its documentation.
Thanks to Benjiko99 for notifying me about this change.
- Expose a
CoroutineScope
as the receiver of the lambda passed toexecute
methods - Mark
ioContext
as@InternalRainbowCakeApi
instead of using deprecations on it - Migrate to an up-to-date Maven publishing setup
- Removed jcenter as a dependency
- Android Gradle Plugin 4.2.1
- Kotlin 1.5.10
- Coroutines 1.5.0
- AppCompat 1.3.0
- Material 1.3.0
- Lifecycle 2.3.1
- Dagger 2.36
- Koin 3.0.2
- ConstraintLayout 2.0.4
Nineteenth release of RainbowCake.
RainbowCake now ships two classes for supporting special kinds of Fragments. Using the new RainbowCakeDialogFragment
and RainbowCakeBottomSheetDialogFragment
(now isn't that a mouthful!) classes works very similarly to using RainbowCakeFragment
.
Thanks to julienherrero for their contribution that kicked this off.
- Kotlin 1.4.20
- Android Gradle Plugin 4.1.1
Eighteenth release of RainbowCake.
Previously, this base Fragment strictly required overriding the getViewResource
method, and inflated the layout returned from that method in onCreateView
, which was also mandatory to call if overridden.
This was inconvenient when using View Binding or Date Binding, so now:
getViewResource
is no longer abstract, instead it returns0
by default. If you don't overridegetViewResource
, you must overrideonCreateView
.- Overriding
onCreateView
no longer requires a call to the super method.
This annotation is only for internal use between RainbowCake's modules. Previous visibility hacks have been replaced with this new annotation. You should generally avoid opting into its usage, as it's not guaranteed public API.
The coroutineScope
used by RainbowCakeViewModel
is now exposed as @InternalRainbowCakeApi
for extensions that need access to this scope.
- Removed the
Application
receiver of therainbowCake
configuration function, to make it easier to call
- Kotlin 1.4.10
- Android Gradle Plugin 4.1.0
- Dagger 2.29.1
- Gradle wrapper 6.7
- AndroidX libraries to latest versions
Seventeenth release of RainbowCake.
The project is now compiled with Kotlin 1.4, the latest stable version of Kotlin 🎉. This cleaned up a bit of the implementation, and most importantly, all library modules now have explicit API mode enabled, ensuring that all public API is explicitly marked as public.
- Fixed an issue with built-in logging #16 (Thanks to Tamás Vágó!)
- Updated target and compile SDK versions to 30
- Version updates for various dependencies
Sixteenth release of RainbowCake.
A handful or previously deprecated pieces of code are now removed. If you want to migrate away from them, use 0.7.0
and IDE migration features before upgrading to 1.0.0
.
JobViewModel
: replace usages withRainbowCakeViewModel
RainbowCakeViewModel#postEvent
: update view state from the UI thread instead _ Navigation extensions inco.zsmb.rainbowcake.extensions
: use the methods from theco.zsmb.rainbowcake.navigation.extensions
package instead- The
rainbow-cake-channels
artifact: use Flows instead
- ViewModels are now initialized in
onCreate
instead ofonAttach
- Decoupled event dispatches, no synchronous, blocking dispatch anymore for either state or events
- Improved internal logging
- Updated Koin & Dagger to latest version
- Updated visibility of a lots of things
- Optimizations, more inline methods and helpers
- Code documentation updates
- Small bugfixes
Fifteenth release of RainbowCake.
RainbowCake has been migrated to AndroidX dependencies instead of the support libraries, and no longer supports projects built on the support library. A year and a half after release of AndroidX, this seemed like a reasonable time to make the jump.
The JobViewModel
class that provides the coroutine integration of the framework (via execute
) is now deprecated, and will be removed in a couple releases. Its functionality has been pulled up into the RainbowCakeViewModel
base class, which now handles view state, events, and coroutine support. Having just one ViewModel base class in the framework should make things less confusing.
Please replace any usages of JobViewModel
with RainbowCakeViewModel
(you get IDE support for this, so it should be trivial).
The rainbow-cake-channels
module has been deprecated, and will be removed in an upcoming release entirely. Coroutine Flows should replace most usages of channels at this point.
- Some method visibilities have been restricted in
RainbowCakeFragment
. These method should not be called anywhere outside of descendants of theFragment
. - The
observeStateAndEvents
testing function now has a variant that can observe queued events, in addition to view state and events. - The
requireArguments
method is now deprecated, as the AndroidXFragment
class includes the method, making the extension unnecessary. - Added some new unit tests for
SingleShotLiveData
. - Small project configuration updates, dependency version bumps, etc.
Fourteenth release of RainbowCake.
- The reified
popUntil
extension used to returnUnit
instead ofBoolean
, which is what the original method returns to indicate if popping happened. Fixed! - ViewModel instances are now set in
onAttach
instead ofonCreate
. Because there was no real reason to wait untilonCreate
to do this. ViewModelProviders
has been deprecated, so nowViewModelProvider
is being used directly.- Version updates (Gradle 6.2.2, AGP 3.6.1, Kotlin 1.3.70).
- Revamped publishing setup for the libraries.
Thirteenth release of RainbowCake.
Instead of implementing CoroutineScope
in JobViewModel
, it now contains a CoroutineScope
instance, which falls in line with many recommendations regarding scopes, as well as the classic advice of favouring composition over inheritance.
This should be an internal only change, but if you've been abusing the scope interface on ViewModels to launch coroutines on them from Fragments, it's a potentially breaking change.
The architecture now ships with a dedicated testing module, which supports unit testing the architecture (Yay! 🎉). This testing module is in an experimental status, as it itself relies on experimental coroutine testing libraries at this point.
For Presenter tests, you can use the PresenterTest
base class, which will replace the IO dispatcher used in Presenters with the test dispatcher, to make it execute immediately:
For ViewModel tests, you can use the ViewModelTest
base class, which will replace the Main dispatcher used by the execute
method with the test dispatcher, and also replace the internal LiveData
executor with a mock executor that executes everything on a single thread, in a blocking manner.
The difficult part of testing ViewModels is having to observe their reactions to inputs through various LiveData
-based mechanisms - both state changes and events work this way. To make this easy, the rainbow-cake-test
library provides the observeStateAndEvents
function that lets you assert changes to state, as well as any emitted events:
vm.observeStateAndEvents { stateObserver, eventsObserver ->
vm.loadArticle(1L)
stateObserver.assertObserved(Loading, ArticleLoaded())
vm.loadArticle(-1L)
eventsObserver.assertObserved(InvalidIdError)
}
See the extensions on the MockObserver
class for the currently available assertions. Note that you can also add your own assertion extensions on this class, as needed.
Testing other, lower level components such as Interactors or Data Sources should not require additional support from the architecture. You can use the experimental coroutines test library to wrap such tests in runBlockingTest
calls.
Twelfth release of RainbowCake.
The events
property of RainbowCakeViewModel
(and consequently, its LiveDataCollection
type) are now exposed for testing purposes only.
Eleventh release of RainbowCake.
The contentFrame
ID that's used in the activity_main
layout is now declared in a separate XML file to avoid crashes produced by it not being present if the layout is overridden.
Tenth release of RainbowCake.
Not much!
- Dagger 2.24 (incremental by default!)
- Kotlin 1.3.50
- Coroutines 1.3.0
- Gradle 5.6
- Android Gradle plugin 3.5.0
The pop
and popUntil
methods now have documented return values.
Ninth release of RainbowCake.
Version 0.4.0 caused some unexpected behaviour when reading the viewState
immediately after setting it to a new value, as continuously blocking the thread between these two operations didn't allow the set operation to complete, therefore the read showed an outdated state. This should be fixed now.
The postState
method is now even more deprecated than before. You should really only update state via viewState
and from the UI thread. Please.
Eight release of RainbowCake.
Dagger related code has been moved to a separate artifact, which you can include the following way:
implementation "co.zsmb:rainbow-cake-dagger:$rainbow_cake_version"
Some imports have also been changed to reflect this. Here's a quick table of what you'll need to migrate (should be just a quick search & replace, so no script this time):
Original | Replacement |
---|---|
co.zsmb.rainbowcake.di.RainbowCakeComponent |
co.zsmb.rainbowcake.dagger.RainbowCakeComponent |
co.zsmb.rainbowcake.di.RainbowCakeModule |
co.zsmb.rainbowcake.dagger.RainbowCakeModule |
co.zsmb.rainbowcake.di.ViewModelKey |
co.zsmb.rainbowcake.dagger.ViewModelKey |
co.zsmb.rainbowcake.RainbowCakeApplication |
co.zsmb.rainbowcake.dagger.RainbowCakeApplication |
co.zsmb.rainbowcake.base.getViewModelFromFactory |
co.zsmb.rainbowcake.dagger.getViewModelFromFactory |
Why mess around with all that Dagger stuff? Because it's no longer the only game in town. You may now also use Koin 2.0 for your dependency injection needs with RainbowCake.
-
Include the new Koin artifact:
implementation "co.zsmb:rainbow-cake-koin:$rainbow_cake_version"
-
Replace Dagger with Koin in your dependencies, here are some recommended artifacts:
def koin_version = '2.0.1' implementation "org.koin:koin-core:$koin_version" implementation "org.koin:koin-android:$koin_version" implementation "org.koin:koin-android-viewmodel:$koin_version"
-
Set up Koin. You won't need
@Inject
annotations any more, but you'll have to declare modules and start up your Koin (ideally, in yourApplication
'sonCreate
method). See the getting started guide for more details.
Note: You may also use the two DI solutions within the same RainbowCake project simultaneously, for example if you're migrating a project piece by piece. Even shared ViewModels should work!
The project is now licensed under Apache 2. One more step towards proper open source.
The screen template now has an option to generate new screens that are powered by Koin, and both the screen and ListAdapter templates now support AndroidX!
Seventh release of RainbowCake.
Channel related code has now been moved from the core library to the rainbow-cake-channels
artifact. It also includes a new feature, converting a LiveData
instance to a Channel
:
fun getNews(): ReceiveChannel<List<News>> {
return newsDao.getNews().toChannel()
}
This, as with other Channel related API, is experimental (but seems to work alright).
(Flow support of various forms is also coming soon.)
Argument handling extensions have been moved from the core library to the navigation artifact. Their package names have also been changed to reflect this change. The old extensions are now deprecated.
The previously used documentation repo is now deprecated. See rainbowcake.dev instead.
- Coroutines 1.2.0
This release contains many structural changes, and some new features. However, it contains no critical bugfixes. This means that if you don't want to suffer the cost of updating to this version right now, the previous version should keep working for you just fine.
If you keep using the old version and do find critical issues in it, report the issue, and a bugfix release using the old structure and package names will be provided for you, if necessary.
Updating your project is going to be just a little bit more effort than usual (should still be about 2 minutes using Studio, really).
-
Update your Gradle dependencies (as necessary, see the Repackaging section):
def rainbow_cake_version = '0.2.0' implementation "co.zsmb:rainbow-cake-core:$rainbow_cake_version" implementation "co.zsmb:rainbow-cake-navigation:$rainbow_cake_version" implementation "co.zsmb:rainbow-cake-timber:$rainbow_cake_version"
-
Perform the following search and replace actions in your project - Ctrl + Shift + R:
| Original | Replacement | | ------------------------| ------------------------ | |
hu.autsoft.rainbowcake
|co.zsmb.rainbowcake
| |BaseViewModel
|RainbowCakeViewModel
|
|BaseFragment
|RainbowCakeFragment
| |BaseActivity
|RainbowCakeActivity
| |BaseApplication
|RainbowCakeApplication
| |BaseModule
|RainbowCakeModule
| |BaseComponent
|RainbowCakeComponent
|There is also a migration script available to perform this search and replace task. This script does its best to safely migrate your project, but be sure to check the changes it makes.
The script can be invoked in the following way:
./rc-migration.sh ~/projects/MyProject
Or for example, on Windows, from a Git bash:
./rc-migration.sh /c/projects/MyProject
-
Add configuration to your project as necessary (see the New configuration options section for details).
Note that previous versions of the library logged internal events to Timber by default, while the new configuration feature disables internal logging by default. This means you won't see stacktraces of uncaught
Exception
s caught byJobViewModel
anymore. If you wish to re-enable previous behaviour, set the following configuration options (again, details explained below):rainbowCake { isDebug = BuildConfig.DEBUG logger = Loggers.TIMBER }
-
Update your Android Studio templates (should be just a simple
git pull
).
Read on to see the explanation of why all these steps are required.
The package names of the framework, as well as the artifact group IDs have been changed from hu.autsoft
to co.zsmb
.
The framework is also no longer being published as a -SNAPSHOT
. These are now regular, stable releases (albeit non-final, because nothing ever is).
As a modularization effort, the framework is being split up into multiple artifacts - only three, for now. This means including three separate Gradle dependencies in your project, if you actually need the features from all of them.
The currently available artifacts are:
implementation "co.zsmb:rainbow-cake-core:0.2.0"
Contains everything from previous versions, except for the navigation features.
implementation "co.zsmb:rainbow-cake-navigation:0.2.0"
Contains all the navigation features that were part of the base artifact before.
implementation "co.zsmb:rainbow-cake-timber:0.2.0"
You only need this artifact if you want the framework to log about its internal events (this is mostly just the exceptions caught by JobViewModel
), and you want it to do so using Timber. See details below.
The base classes BaseViewModel
, BaseFragment
, and BaseActivity
have been renamed to RainbowCakeViewModel
, RainbowCakeFragment
, and RainbowCakeActivity
, respectively.
This change makes the Base*
names available for applications using the framework, so that they may create their own Base*
classes that inherit from the framework classes, and include any app-specific extra behaviour there.
The framework now has a configuration DSL, which can be invoked in the onCreate
method of your Application
class.
Its usage looks like the following:
override fun onCreate() {
super.onCreate()
rainbowCake {
isDebug = false
logger = Loggers.NONE
consumeExecuteExceptions = true
}
}
The available settings, and their possible values:
isDebug
: Boolean,false
by default.- If set to false, it disables all internal logging of the framework, regardless of the setting of
logger
. May affect other behaviour in the future as well (in debug mode, prod behaviour will definitely not change). Recommended value isBuildConfig.DEBUG
.
- If set to false, it disables all internal logging of the framework, regardless of the setting of
consumeExecuteExceptions
: Boolean,true
by default (to keep existing behaviour).- Determines whether the
execute
method inJobViewModel
should catch and log any uncaught exceptions in coroutines, or let them crash the app. Recommended to be set tofalse
at the very least for debug builds, and should be considered even for production.
- Determines whether the
logger
- Determines how the framework should log its internal events. Available options by default are
NONE
(as in no logging) andANDROID
(logs to Logcat viaLog.d
). - If the
rainbowcake-timber
dependency is included,TIMBER
may also be used to log via Timber. Note that this doesn'tplant
anyTree
s, you still have to do that yourself.
- Determines how the framework should log its internal events. Available options by default are
Event handling has been significantly reworked under the hood, since they were quite broken in some edge cases.
- When using shared
ViewModel
instances with scopes, only a single one of the attachedFragment
would receive the events, chosen randomly. - If a
Fragment
was inactive (in the background) while itsViewModel
posted events, only the last event posted would be delivered when it became active again, due to the nature ofLiveData
.
For the first issue: the new events mechanism ensures that all attached Fragment
s receive each event, so that they may each react to it as appropriate.
As for the second problem, you may now decide whether an event only makes sense for the Fragment
to receive immediately (most events will fall in this category!), or if they should be remembered if the Fragment
is not currently active, and delivered later.
Both of these types of events will still be received in the onEvent
method of your RainbowCakeFragment
or RainbowCakeActivity
, but you have to send them in different ways.
Events that should only be delivered immediately should still implement the OneShotEvent
marker interface, and be sent using postEvent
, just like before. (One small caveat: this method can now only be called from the UI thread, which you should already have been doing anyway.) If you send one of these events when the Fragment
is not active, it will never be delivered.
99% of the time, this is the behaviour you need for your events, and the type of events you should use.
Events that matter even if they can't be delivered immediately have to implement the QueuedOneShotEvent
marker, and be sent using postQueuedEvent
. If the observing Fragment
isn't currently active, the event will be queued, and all queued events will be delivered immediately when the Fragment
becomes active again.
Each Fragment
instance has its own independent queue of events. Note that Fragment
s in the background can be destroyed and recreated by the framework, and their queues will be lost in this case - this is a best effort mechanism.
If all of this looks confusing at first, the good news is that you probably don't need all this! You can just keep using events like before, and they'll keep working. They're just much more reliable now.
The framework used to include the multidex support dependency and initialize MultiDex in BaseApplication
by default. Forcing this on applications in this form was a mistake (most notably since apps targeting API 21+ don't need these to use multidex) and has now been removed.
Any apps targeting API levels below 21 should now perform these steps for themselves, if they require multidex.
The popUntil
navigation method can now be used with a reified type parameter instead of a KClass
parameter. So instead of navigator?.popUntil(HomeFragment::class)
, you can now navigator?.popUntil<HomeFragment>()
!
A convenience change in ViewModel
scoping: before, only Activity
scoped ViewModel
instances could have keys. Now you can also key ViewModel
s scoped to a parent Fragment
.
The syntax for non-keyed ParentScope
remains the same as before:
override fun provideViewModel() = getViewModelFromFactory(scope = ParentFragment)
And the optional key can be provided in the parameter:
override fun provideViewModel() = getViewModelFromFactory(scope = ParentFragment("some_key"))
withArgs
has been replaced with applyArgs
roughly four months ago, therefore using withArgs
is now an outright error, and doesn't just produce a suppressible warning. An intention action to perform this migration via Alt + Enter is still available.
The Contexts
object that actually contained Dispatcher
instances has now been removed, and the library uses Dispatchers
directly instead. RCDispatchers
.
Note that the withIOContext
method is still available.
Client code shouldn't really use this object directly, so in theory, this shouldn't break anything.
- Dagger
2.17
. - Android Gradle plugin
3.3.2
Fifth snapshot release of RainbowCake.
Previously, sequences of Navigator
method calls have always executed individually, e.g. take this call:
navigator?.run {
popUntil(HomeFragment::class)
add(SomeFragment())
}
Here, the current Fragments on top of HomeFragment
would have first been removed, HomeFragment
appeared for a split second, and then SomeFragment
would be added on top.
You can now prevent this "flashing" behaviour by calling navigator.executePending()
after a series of actions, like so:
navigator?.run {
popUntil(HomeFragment::class)
add(SomeFragment())
executePending()
}
- Kotlin 1.3.21
- Android Gradle plugin 3.3.1
Fourth snapshot release of RainbowCake.
Update your dependency version:
implementation 'co.zsmb:rainbow-cake:0.1.1-SNAPSHOT'
New methods have been added to support Fragment arguments with Int
and Serializable
types. Note that the latter of these still isn't a recommendation to pass around large objects as arguments, it's only meant to serve as a way to pass around some small objects like java.util.UUID
easier, without having to convert it to a String
and back.
These, again, conform to the naming convention of existing argument handling methods, and you can see them all here.
All of these Bundle
methods are also now documented and tested according to their documented behaviour. (Their internal implementation has also been unified to simplify them and make them safer.)
The Navigator
interface now has a setStack(Iterable<Fragment>)
method to complement the existing setStack(vararg Fragment)
method, and avoid having to convert List
s and other iterables to arrays.
- Kotlin 1.3.20
Third snapshot release of RainbowCake.
Update your dependency version:
implementation 'co.zsmb:rainbow-cake:0.1.0-SNAPSHOT'
Optionally, update your screen templates, which can now be done in a more convenient way via a simple git clone
.
ViewModels by default are scoped to their Fragment, meaning a new instance is created for every new instance of the Fragment (barring configuration changes), and they are cleared when their Fragment is destroyed (as in their lifecycle completely ends).
There are use cases where it would make sense to share ViewModel instances between Fragments, and the getViewModelFromFactory
method now provides an opportunity for this in the form of an optional parameter. ViewModels may now be scoped to the current Activity or to a parent Fragment. For details, see the documentation here.
A demo showcasing a ViewPager
where the pages share a ViewModel scoped to their parent Fragment is also available here.
The Navigator
provided by the library contains a fade animation between screen changes by default. It's not possible to override this default behaviour.
You can override it globally, by providing new values for certain properties in your Activity that inherits from NavActivity
:
class MainActivity : SimpleNavActivity() {
override val defaultEnterAnim: Int = R.anim.slide_in_right
override val defaultExitAnim: Int = R.anim.slide_out_left
override val defaultPopEnterAnim: Int = R.anim.slide_in_left
override val defaultPopExitAnim: Int = R.anim.slide_out_right
}
You can also override animations one by one, by using overloads of the add
and replace
methods:
navigator?.add(SomeFragment(),
enterAnim = R.anim.slide_in_right,
exitAnim = R.anim.slide_out_left,
popEnterAnim = R.anim.slide_in_left,
popExitAnim = R.anim.slide_out_right
)
Note that a simple 0
may be used for any of these values to disable an animation altogether.
Be sure to check the documentation for all of the properties and methods mentioned above, as they contain much more information.
New methods have been added to support Fragment arguments with Boolean
and Parcelable
types. These conform to the naming convention of existing argument handling methods, see them here and here.
Previously, any coroutines started by making execute
calls in the ViewModel
were only cancelled when the ViewModel
was cleared. If you need to manage the Job
representing coroutines manually instead, you can now do so with the executeCancellable
method:
class MyViewModel : JobViewModel<MyViewState>(Default) {
private var loadingJob: Job? = null
fun loadData() {
loadingJob?.cancel()
loadingJob = executeCancellable {
// do something
}
}
}
Note that you shouldn't ever return this Job
to your Fragment, so do not do this, as the loadData
method here has an implicit return type of Job
, instead of Unit
:
class MyViewModel : JobViewModel<MyViewState>(Default) {
fun loadData() = executeCancellable {
// do something
}
}
- Fragment backstack management fixes around the
replace
operation of theNavigator
implementation. - The
rootJob
inJobViewModel
is now aSupervisorJob
so that it's not cancelled altogether if a child coroutine fails (based on this article's advice). - The
coroutineContext
used by theCoroutineScope
inJobViewModel
is now only created once at instantiation. - Channel observations are now explicitly cleared when a
ChannelViewModel
is cleared.
- Kotlin 1.3.11
- Coroutines 1.1.0
- Android Gradle Plugin 3.3.0