Insetter and other updates on our Template
Last time we talked about our Android template we made a major update migrating from Java to Kotlin. This time we updated the existing libraries, added a new one, while also adding a few functions that we found useful when developing our latest project.
Libraries updates
In the past months we fixed bugs and changed some mechanics in our base libraries. The library that suffered the most changes was Dagger Hilt.
We saw the deprecation ApplicationComponent with 2.31 and the addition of TestInstallIn.
ApplicationComponent was removed as of 2.32, so if you used our template to start your Project or have issues with ApplicationComponent simply replace it with the SingletonComponent.
While UninstallModules can still be used, we recommend migrating to TestInstallIn. It allows global test replacement modules to be defined, instead of using on individual tests.
An example of TestInstallIn can bee seen below, where we replace ProductionDataServiceModule with FakeDataServiceModule.
@Module
@TestInstallIn(
components = SingletonComponent::class,
replaces = ProductionDataServiceModule::class)
interface FakeDataServiceModule {
@Binds fun bind(impl: FakeDataService): DataService
}
This information and example can be explored in more detail at https://dagger.dev/hilt/testing#testinstallin
New Library
Previously we were using a helper class to handle WindowsInsets. While this class still works, it's no longer the recommended way to do it, having a few of its dependencies deprecated. However we did find an open source solution, Insetter, built by @chrisbanes.
Insetter can be used in several ways, having 2 extensions built in addition to the main library. Bellow you can find small examples and descriptions, but we recommend taking a look at the official documentation at https://chrisbanes.github.io/insetter/, where most of our information was taken from.
Main Library
The main library provides simple APIs for handling WindowInsets. To use the Library you can use its main entry point, the Builder
Insetter.builder()
.padding(windowInsetTypesOf(navigationBars = true))`
.margin(windowInsetTypesOf(statusBars = true))
.applyToView(view)
or with kotlin you can also
view.applyInsetter {
type(navigationBars = true) {
padding()
}
type(navigationBars = true) {
margin()
}
}
Data Binding extension
Providing Data Binding-specific functionality that works with any view and allowing for a more clean code based. Using data binding, all you need is to modify your xml's where you intend to
<ImageView
app:paddingBottomSystemWindowInsets="@{true}"
app:paddingLeftSystemWindowInsets="@{true}" />
And to have a better Edge-to-edge you can also use the attribute app:layout_edgeToEdge that
sets this view's system-ui visibility with the flags required.
Widgets extension
An extension library which provides versions of commonly used ViewGroups with enhanced inset handling. Currently this library is focusing on building upon ConstraintLayout. Currently there are two widgets you can use.
ConstraintLayout: InsetterConstraintLayout(subclass of Constraint Layout)
<dev.chrisbanes.insetter.widgets.constraintlayout.InsetterConstraintLayout
xmlns:android="<http://schemas.android.com/apk/res/android>"
xmlns:app="<http://schemas.android.com/apk/res-auto>" ...>
<ImageView
android:id="@+id/image"
app:paddingSystemWindowInsets="left|top|right|bottom"
... />
</dev.chrisbanes.insetter.widgets.constraintlayout.InsetterConstraintLayout>
or InsetterConstraintHelper which is a constraint helper.
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="<http://schemas.android.com/apk/res/android>"
xmlns:app="<http://schemas.android.com/apk/res-auto>" ...>
<dev.chrisbanes.insetter.widgets.constraintlayout.InsetterConstraintHelper
app:paddingSystemWindowInsets="left|top|right|bottom"
app:constraint_referenced_ids="image,button" ... />
<ImageView
android:id="@+id/image" ... />
</androidx.constraintlayout.widget.ConstraintLayout>
While the template comes with both extensions there's no point in having both the Widgets and the Data Binding extension. So If you're already used to use data-binding we recommend using the dbx library and its binding adapters, since they work with any view type.
Result Helper Functions
To better handle potential exceptions and errors we usually wrap the Flows objects with Result. And to keep the code cleaner and consistent we introduced a few helper functions.
toResult
foldOnEach
mapIfSuccess
flatMapConcatIfSuccess
toResult
wraps the flow object and guarantees that any Throwable is also wrapped in a (Result
) Failure
, making sure the code is free of crashes and properly handles errors.
Last but not least, foldOneEach
will allow you to handle both Success
and Failure
each with a different function.
buttonPressed
.asFlow()
.flatMapConcat { domainClass.foo() }
.toResult()
.foldOnEach(
{
output.send(it)
}, // Success
{ e ->
errors.send(e)
} // Failure
)
.launchIn(ioScope)
Like the name suggests ifSuccess functions will only run on (Result) Success, and propagate errors.
📲 We are a studio specialized in designing and developing Android products. Keep up with us through Twitter, Facebook, and Instagram.