Sample app to try compose and showcase principles from Composing (UI) beyond the UI

Overview

Composing clocks sample app

This is a sample app to show how to build an app that follows the practices described in the series of articles Compose (UI) beyond the UI.
These practices, in short, propose manually handling configuration changes and avoiding AAC ViewModels in apps that are fully in compose. To understand why, please read the articles (Part I, Part II, Part III).
This app uses mock data to save time on API requests, and to avoid needing credentials.

Project characteristics

  • Compose UI for everything (beta01)
  • MVVM architecture
  • No usage of AppCompatActivity, Fragment or AAC ViewModel
  • Config changes are manually handled: language or orientation changes do not recreate the activity
  • Koin for DI and scoping screens and nav-graphs
  • Coroutines and flow
  • App state is saved, with utils that simplify usage with flow
  • ViewModels that are scoped to screens, to screen-flows (nested graph) and to the whole app
  • Navigation component (with helpers to manage koin scopes and route creation)
  • Layout that adapts for landscape or multi-window (with a simple animation)
  • Custom clock that can be themed
  • Toggle to force a different locale without recreating the activity
  • Bottom bar navigation that keeps correct selection on sub-screens
  • Some simple animations (when resizing multiwindow in config screen, when time passes in clock, when choosing a clock type, or in picture previews)
  • Coil with accompanist to display images

App description

This app has a bottom navigation bar with three destinations. The first one is a configuration screen where you can change the language and theme the clock (change the color of some of its parts).
The second screen shows a list of cities with a picture and a clock that shows the local time. Clicking on a city takes the user to the screen detail where they can see the timezone and a description of the city.
Finally, the third bottom bar destination allows the user to add a city to the list. It consists of a flow of two screens, and it is possible to navigate back and forth without losing the data. In the first screen of this flow it is possible to write the city name, and select the timezone and clock style. Then, on the second screen the user can introduce a description and an URL for the picture, which can be previewed. Adding the city takes the user to the city list, with the new addition.

Composing Clocks Resizing
App demo Resizing

Non-AAC ViewModels (or ViewControllers)

To build our own ViewModels we need the following:

  • A way to retrieve the same instance while its lifecycle is alive, or in other words, while the user has not left the screen or view.
  • A way to clear it when its lifecycle ends, to cancel ongoing tasks like coroutines.
  • A way to save user state.

To obtain the same instance of the ViewController while its lifecycle is alive, in this sample Koin's scopes are used: getOrCreateScope. To know what instance to retrieve, the navigation path with the parameters filled is used as id.
To clear the ViewControllers and other scoped instances that require clearing, those instances implement the Clearable interface and are declared with bind Clearable::class in Koin.
To cancel the scope when the user exits a destination we have the utility RunOnce (which uses rememberSaveable) and then set a LifecycleEventObserver to the NavBackStackEntry of the destination.
To aid in this process there are some helpers that are further explained in navigation helpers.

Finally, to save state in ViewModels tied to a navigation destination we can retrieve a SavedStatHandle from the NavBackStackEntry. For other cases, compose provides rememberSaveable. To unify saving state this app has the interface StateSaver with two implementations, one for each case. This way the classes that need to save state do not have to worry about concrete implementations, and state saving is platform agnostic.

Forcing a locale

The app allows changing the language throuth a toggle (only Spanish and English). The language change is done without recreating the activity, which is faster and avoids flashing the screen.
In newer versions of Android, to force a change of locale you are supposed to use attachBaseContext, which relies on Activity recreation to work.
The alternative is to use resources.updateConfiguration, which is deprecated. This works without recreating the Activity, but it is deprecated due to problems encountered in WebView with night mode needing to change the resources configuration.
In compose there is a new way of changing the language, which is using CompositionLocalProvider and providing an updated context and layoutDirection. As of beta01, however, the bottom navigation bar in scaffold does not get updated when the locale is changed. It only gets updated when the screen orientation changes, for example.
I have opened this issue that you can star if it affects you, and currently I use the deprecated method. You can find an example with the deprecated approach in SetLanguage, and an example of the compose-like alternative -that does not work properly yet- in LanguageOverride.

Navigation helpers

This sample provides utilities to help handling scopes for screens and nested graphs in navigation. These helpers require using the provided models that help build navigation paths.
If you know how to navigate with Navigation component and how to use Koin scopes, the following sections describe the small changes you will need to apply, if you choose to use these helpers. Basically, you will need to use different functions to declare your destinations, declare classes to define each destination (that extend the proper parent), and declare Koin scopes in a specific manner. In exchange, you will get a scope that is automatically handled for your navigation destinations (screens and nested graphs); and an opinionated way of declaring destinations and arguments, that helps in building the paths.

Declaring a screen in a NavHost

It can be done with this syntax: scopedComposable(YourDestination) { navEntry, scope ->, where YourDestination is an instance of a class that extends NavDestination.
The scope parameter is a Koin scope that can be used to retrieve instances that are bound to the scope of the screen, like the ViewModel or other ViewController.

Declaring a nested graph in a NavHost

It can be done with this syntax: scopedNavigation(YourSubgraph) { nestedNavGraph ->, where YourSubgraph is an instance of a class that extends NestedNavGraph.
To declare destinations inside the nested graph you can use this:

doubleScopedComposable(
    navController, nestedNavGraph, YourDestination
) { navEntry, parentScope, scope ->

Here YourDestination is also an instance of a class that extends NavDestination, and the nestedNavGraph is the parameter provided by scopedNavigation. You get two scopes, one for this screen and another for the nested graph. This way, if you need to scope a ViewController (or something else) to the whole screen flow it can be done with that scope.

Defining a screen destination

To declare a screen destination create an object that extends ScreenDestination or ScreenDestinationWithArgs, and specify the navigation route, the list of arguments (as NavArguments) and the params class.
The params class must extend NavParams and combine its parameters into a list in the same order as declared in the Destination arguments. If an argument is optional, to build the list use this: YourNavArgument.paramsAsRoute(paramVariable).

Defining a nested graph destination

To declare a nested graph destination create an object that extends SubgraphDestination or SubgraphDestinationWithArgs, and specify the navigation route, the list of arguments (as NavArguments), the start destination, and the params class for the subgraph and for the start destination.

Navigating to a destination with no parameters

You can use navController.navigate(YourDestination.buildRoute(NoParams)), or the less explicit YourDestination.declaredPath.

Navigating to a destination with parameters

You can pass YourDestination.buildRoute(DestinationNavParams(firstParam, secondParam)) to the navigate function.

Navigating to a destination with optional NavParams

If you have optional params you should declare them as OptionalParam in your NavParams. Then use OptionalParam.Provided(id) or OptionalParam.Default accordingly.

Declare a Koin scope for a screen

Pass the ScreenDestination as scope qualifier. Then in all your scoped instances that require a callback when the scope is cancelled, implement Clearable and declare them with bind Clearable::class. Example:

scope<YourScreenDestination> {
    scoped { YourViewModel() } bind Clearable::class
}

Declare a Koin scope for nested graph

Instead of scope you can use navGraphScope , which handles the lifecycle automatically. Then you can declared your scoped objects inside, as in the previous section.

You might also like...
Collection of Compose UI Templates
Collection of Compose UI Templates

Compose UI Templates This repository contains set of Android compose ui templats which helps you to learn compose in Android. 💻 Requirements To try o

Scrollbar Indicator usign compose
Scrollbar Indicator usign compose

Carousel Carousel is a scroll indicator for ScrollableState composes. Carousel can be vertial or horizontal indicator based on the size provided using

A crypto currency wallet UI/UX template created using Android's jetpack compose.
A crypto currency wallet UI/UX template created using Android's jetpack compose.

CryptoWalletTemplate A crypto currency wallet UI/UX template created using Android's jetpack compose. High definition demo on youtube Youtube video de

Sliders for compose with custom styles
Sliders for compose with custom styles

Custom compose sliders This package allows you to build highly customizable sliders and tracks for compose for android Add to your project Add it in y

Card with swipe options in Jetpack Compose

SwipeableActionCard Card with swipe options in Jetpack Compose Tutorial: Click Here Import SwipeableActionCard library Add this in project level build

Android jetpack compose swipe library

Swiper for Android Jetpack Compose Android Jetpack Compose swipe library. Downlo

A color picker for Jetpack compose 🎨
A color picker for Jetpack compose 🎨

Compose Color Picker 🎨 Color Picker for Jetpack compose A color picker for Jetpack compose 🎨 Download 🚧 Working on it 🚧 Usage ColorPicker { co

A tiny window overlay to log app internal on top of your android app
A tiny window overlay to log app internal on top of your android app

DebugOverlay A logcat alike overlay to display log messages in your app as independent overlay. Typically in android you would log some app internals

This a Ride User app in which in integrate almost every features of a Ride Share app.
This a Ride User app in which in integrate almost every features of a Ride Share app.

My-Ride-App This a Ride app(User) in which I integrate almost every features of a Ride Share app. Here I use kotlin programing language, Google map SD

Comments
  • Bottom nav is behaving wierdly

    Bottom nav is behaving wierdly

    Hi,

    I am trying to use the nav system you used in this project in one of mine. But I am facing some issues. It'd be great if you could help me out.

    I have two subgraphs - Instrument and Transaction, which are also shown on the bottom bar. When I press instruments, it does not navigate to instrument, but starts switching between instrument and transaction. I've attached a screen recording below.

    Here is a link to project repo

    ezgif com-gif-maker

    opened by hardiksachan 1
Owner
Jordi Saumell
Jordi Saumell
The ShowcaseView library is designed to highlight and showcase specific parts of apps to the user with an attractive and flat overlay.

The ShowcaseView library is designed to highlight and showcase specific parts of apps to the user with an attractive and flat overlay.

Mohammad Reza Eram 484 Dec 26, 2022
A simple and Elegant Showcase view for Android

Tuto Showcase A simple and Elegant Showcase view for Android TutoShowcase.from(this) .setContentView(R.layout.tuto_sample) .on(R.id.about) //

Florent CHAMPIGNY 509 Nov 25, 2022
Android swipe-to-dismiss mini-library and sample code

Android Swipe-to-Dismiss Sample Code Sample code that shows how to make ListView or other views support the swipe-to-dismiss Android UI pattern. See t

Roman Nurik 1.3k Dec 29, 2022
This is a sample Android Studio project that shows the necessary code to create a note list widget, And it's an implementation of a lesson on the Pluralsight platform, but with some code improvements

NoteKeeper-Custom-Widgets This is a sample Android Studio project that shows the necessary code to create a note list widget, And it's an implementati

Ibrahim Mushtaha 3 Oct 29, 2022
A customizable debug screen to view and edit flags that can be used for development in Jetpack Compose applications

Tweaks A customizable debug screen to view and edit flags that can be used for development in Jetpack Compose applications To include the library add

Guillermo Merino Jiménez 4 Jan 14, 2022
A Jetpack Compose library with blur, pixelate, and other effects to keep your designer happy. Inspired by iOS UIVisualEffectView.

A Jetpack Compose library with blur, pixelate, and other effects to keep your designer happy. Inspired by iOS UIVisualEffectView.

清茶 67 Dec 30, 2022
Compose RevealSwipe

RevealSwipe Current Compose Version: 1.0.0-beta09 Compose RevealSwipe Swipable in both directions Add to your project Add actual RevealSwipe library:

Alexander Karkossa 109 Dec 28, 2022
MacOS theme for JetBrains Compose UI framework

MacOS theme for Compose Multiplatform MacOS theme written in Compose UI. Create native looking UIs for Mac using Compose UI. Share your UI code to run

Carson Holzheimer 204 Nov 19, 2022
PCard Add payment card screen made using JetPack Compose

PCard Add payment card screen made using JetPack Compose Find this repository useful? ❤️ Support it by joining stargazers for this repository. ⭐ And f

Mohamed Elbehiry 61 Dec 16, 2022