Android sample app following best practices: Kotlin, Compose, Coroutines and Flow, Hilt, JetPack Navigation, ViewModel, MVVM and MVI, Retrofit, Coil

Overview

Foodies - Modern Android Architecture

Foodies is a sample project that presents a modern 2021 approach to Android app development.

The project tries to combine popular Android tools and to demonstrate best developement practices by utilizing up to date tech-stack like Compose, Kotlin Flow and Hilt while also presenting modern Android application Architecture that is scalable and maintainable through a MVVM blend with MVI.

Description

Architecture

The project is layered traditionally with a View, Presentation, Model separation and presents a blend between MVVM and MVI inspired from Yusuf Ceylan's architecture but adapted to Compose.

Architecture layers:

  • View - Composable screens that consume state, apply effects and delegate events.
  • ViewModel - AAC ViewModel that manages and reduces the state of the corresponding screen. Additionally, it intercepts UI events and produces side-effects. The ViewModel lifecycle scope is tied to the corresponding screen composable.
  • Model - Repository classes that retrieve data. In a clean architecture context, one should use use-cases that tap into repositories.

As the architecture blends MVVM with MVI, there are a three core components described:

  • State - data class that holds the state content of the corresponding screen e.g. list of FoodItem, loading status etc. The state is exposed as a MutableStateFlow that perfectly matches the use-case of receiving continuos updates with initial values and that are broadcasted to an unknown number of subscribers.

  • Event - plain object that is sent through callbacks from the UI to the presentation layer. Events should reflect UI events caused by the user. Event updates are exposed as a MutableSharedFlow type which is similar to StateFlow and that behaves as in the absence of a subscriber, any posted event will be immediately dropped.

  • Effect - plain object that signals one-time side-effect actions that should impact the UI e.g. triggering a navigation action, showing a Toast, SnackBar etc. Effects are exposed as ChannelFlow which behave as in each event is delivered to a single subscriber. An attempt to post an event without subscribers will suspend as soon as the channel buffer becomes full, waiting for a subscriber to appear.

Every screen/flow defines its own contract class that states all corresponding core components described above: state content, events and effects.

Dependency injection

Hilt is used for Dependency Injection as a wrapper on top of Dagger.

Apart from regular parameter/constructor injection, assisted injection is used in order to inject runtime categoryId parameter to FoodCategoryDetailsViewModel. While regular viewmodel injection was done through the HiltViewModel annotation, FoodCategoryDetailsViewModel was injected through the @AssistedInject annotation.

The dynamic injection of the categoryId was done through defining it as an @Assisted parameter while also providing ViewModelProvider.Factory class for FoodCategoryDetailsViewModel. Additionally, an assisted factory ViewModelAssistedFactory was defined through the @AssistedFactory annotation.

Decoupling Compose

Since Compose is a standalone declarative UI framework, one must try to decouple it from the Android framework as much as possible. In order to achieve this, the project uses an EntryPointActivity that defines a navigation graph where every screen is a composable.

The EntryPointActivity also collects state flows and passes them together with the Effect flows to each Screen composable. This way, the Activity is coupled with the navigation component and only screen (root level) composables. This causes the screen composables to only receive and interact with plain objects and Kotlin flows, therefore being platform agnostic.

Comments
  • [Question] Why is the loading state not displayed after a delay?

    [Question] Why is the loading state not displayed after a delay?

     private suspend fun getFoodCategories() {
            setState { setIsLoading(true) }
            val categories = repository.getFoodCategories()
            setState {
                copy(categories = categories).setIsLoading(false)
            }
            **delay(2_000)**
            **setState { setIsLoading(true) }**
            setEffect { FoodCategoriesContract.Effect.DataWasLoaded }
        }
    

    I'm trying to change the state information but this doesn't work if a delay is applied.

        delay(2_000)
        setState { setIsLoading(true) }
    

    But the effect is well applied for this line.

            setEffect { FoodCategoriesContract.Effect.DataWasLoaded }
    

    Do we know why ?

    All in all, good job for this architecture.

    opened by JustJerem 3
  • [Improvement] Getting a result from the previous Destination

    [Improvement] Getting a result from the previous Destination

    You can remove the AssistedFactory for the viewmodel. Example :

    EntryPointActivity.kt

       composable(
                route = NavigationKeys.Route.FOOD_CATEGORY_DETAILS,
                arguments = listOf(navArgument(NavigationKeys.Arg.FOOD_CATEGORY_ID) {
                    type = NavType.StringType
                })
            ) {
                **val viewModel: FoodCategoryDetailsViewModel = hiltViewModel()**
                val state = viewModel.viewState.value
                FoodCategoryDetailsScreen(state)
            }
    

    FoodCategoryDetailsViewModel.kt

    @HiltViewModel
    class FoodCategoryDetailsViewModel @Inject constructor(
        **stateHandle: SavedStateHandle,**
        private val repository: FoodMenuRepository
    ) : BaseViewModel<
            FoodCategoryDetailsContract.Event,
            FoodCategoryDetailsContract.State,
            FoodCategoryDetailsContract.Effect>() {
    
        **private lateinit var categoryId: String**
    
      init {
            **stateHandle.get<String>(NavigationKeys.Arg.FOOD_CATEGORY_ID)?.let {
                categoryId = it
            }**
    
            viewModelScope.launch {
                val categories = repository.getFoodCategories()
                val category = categories.first { it.id == categoryId }
                setState { copy(category = category) }
    
                val foodItems = repository.getMealsByCategory(categoryId)
                setState { copy(categoryFoodItems = foodItems) }
            }
        }
    ...
    

    So you can delete :

     @Suppress("UNCHECKED_CAST")
        class Factory(
            private val assistedFactory: ViewModelAssistedFactory,
            private val categoryId: String
        ) : ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                return assistedFactory.create(categoryId) as T
            }
        }
    

    And so, you can delete ViewModelAssistedFactory.kt and transform your activity for :

      ComposeSampleTheme {
                    FoodApp()
                }
    

    See official docs : https://developer.android.com/guide/navigation/navigation-programmatic#returning_a_result

    I know that in this case, we use the information to bring the information back to a previous view, but since we have access to the backstack, we can also do that method here.

    I saw it originally in a repo from Mitch Tabian. https://github.com/mitchtabian/Food2Fork-KMM/blob/6ab855181c76cab8ace282513efce2714b2a0b98/androidFood2Fork/src/main/java/com/codingwithmitch/food2forkkmm/android/presentation/recipe_detail/RecipeDetailViewModel.kt#L35

    I can't create branch to make PR.

    opened by JustJerem 2
  • Dark theme background color

    Dark theme background color

    There is a small issue with background color in dark theme becaus of usage of the same color for background both in dark and light pallete. See below image:

    Home | Detail :-------------------------:|:-------------------------: Screenshot_1625833685 | Screenshot_1625833691

    I changed the background color for dark pallete so it looks like this Home | Detail :-------------------------:|:-------------------------: Screenshot_1625833801 | Screenshot_1625833822

    Are you fine with this? If so i will open new request.

    opened by wiryadev 2
  • Migrate from assisted injection to `SavedStateHandle`

    Migrate from assisted injection to `SavedStateHandle`

    In order to avoid assisted injection that brings an overhead of complexity in injecting the ViewModels, we use SavedStateHandle to extract runtime navigation arguments.

    opened by catalinghita8 1
  • [Question] - Reducer

    [Question] - Reducer

    https://github.com/catalinghita8/android-compose-mvvm-foodies/blob/888f46af1015f771a290937d31cc5ec26fe340de/app/src/main/java/com/codingtroops/composesample/base/BaseComponents.kt#L48

    Aren't you sending the previous state? 🤔 I can make it work if I use:

        override fun setState(state: () -> ViewState) {
            _viewState.value = state.invoke()
        }
    

    or just _viewState.value = state. Maybe I'm missing the point 😅

    opened by GuilhE 1
  • hi,please tell me this

    hi,please tell me this

    BaseViewModel: private val _viewState: MutableState = mutableStateOf(initialState) val viewState: State = _viewState

    @Composable private fun FoodApp() { val viewModel: FoodCategoriesViewModel = viewModel() val state = viewModel.viewState.value

    Any changes to value will schedule recomposition of any composable functions that read value. In the case of ExpandingCard, whenever expanded changes, it causes ExpandingCard to be recomposed.

    here dont hava remember,why it can to be recomposed?

    i think it is : val state by remember{ viewModel.viewState.value }

    but no remember,it can be recomposed.

    opened by yuexunshi 0
  • ViewModel unit tests

    ViewModel unit tests

    Hi,

    Really like the implementation but I am struggling to find a way how to test view models. To be precise, how you would assert all values set to compose State<T> instance. Any ideas?

    bug good first issue 
    opened by rsetkus 5
Owner
Software Engineer - Android
null
A demonstration modern Android development project with Jetpack(Compose, Room, Flow, ViewModel, Navigation), Hilt and based on MVVM by using Github API.

A demonstration modern Android development project with Jetpack(Compose, Room, ViewModel, Navigation), Hilt and based on MVVM by using Github API.

Murat 2 Apr 11, 2022
👨‍💻 A demonstration modern Android development project with Jetpack(Compose, Room, ViewModel, Navigation), Hilt and based on MVVM by using Open Sky API. ✈️ 🌍

A demonstration modern Android development project with Jetpack(Compose, Room, ViewModel, Navigation), Hilt and based on MVVM by using Open Sky API.

Ismail Oguzhan Ay 13 Dec 4, 2022
PokeCard Compose is a demo app 100% write in Compose, Flow and Koin based on MVI Clean Architecture 🐱⚡️

A Pokemon Card demo app using Jetpack Compose and Koin based on MVI architecture. Fetching data from the network with Ktor and integrating persisted data in Room database with usecase/repository pattern.

Lopez Mikhael 104 Nov 27, 2022
Viacheslav Veselov 0 Jul 8, 2022
A sample app showing how to build an app using the MVI architecture pattern.

MVI Example This application was streamed live on Twitch to demonstrate how to build an application using MVI. You can find the VOD here for now: http

Adam McNeilly 46 Jan 2, 2023
A sample Android app that demonstrates how to use Firebase Authentication, Crashlytics, Cloud Firestore and Hilt with Jetpack Compose UI

showcase.mp4 Make it So This is a sample Android app that demonstrates how to use Firebase Authentication, Crashlytics, Cloud Firestore and Hilt with

null 107 Dec 31, 2022
🧸 A demo Disney app using Jetpack Compose and Hilt based on modern Android tech stacks and MVVM architecture.

DisneyCompose A demo Disney app using compose and Hilt based on modern Android tech-stacks and MVVM architecture. Fetching data from the network and i

Jaewoong Eum 791 Dec 30, 2022
A sample Grocery Store app built using the Room, MVVM, Live Data, Rx Java, Dependency Injection (Kotlin Injection) and support Dark Mode

Apps Intro A sample Grocery Store app built using the Room, MVVM, Live Data, Rx Java, Dependency Injection (Kotlin Injection) and support Dark Mode In

Irsyad Abdillah 25 Dec 9, 2022
A simple app demonstrates using Jetpack compose with other Jetpack libraries.

Android Pokemon Compose This repository is a simple app that make request to https://pokeapi.co and display them in the paginated lists. It demonstrat

BenBoonya 56 May 21, 2022
learn MVVM & Android Jetpack in Kotlin

MVVM & Android Jetpack MVVM in Android Kotlin Android Jetpack in Kotlin List of tutorials DataBinding : Step 1 mvvm DataBinding : Step 2 mvvm Lifecycl

Alireza 5 Feb 25, 2022
Quality-Tools-for-Android 7.5 0.0 L5 Java This is an Android sample app + tests that will be used to work on various project to increase the quality of the Android platform.

Quality Tools for Android This is an Android sample app + tests that will be used to work on various project to increase the quality of the Android pl

Stéphane Nicolas 1.3k Dec 27, 2022
How to apply meaningful and delightful motion in a sample Android app

Applying meaningful motion on Android How to apply meaningful and delightful motion in a sample Android app Read the complete post at https://medium.c

André Mion 167 Dec 19, 2022
A sample Android app which showcases advanced usage of Dagger among other open source libraries.

U+2020 A sample Android app which showcases advanced usage of Dagger among other open source libraries. Watch the corresponding talk or view the slide

Jake Wharton 5.7k Dec 22, 2022
Create curve bottom navigation using this library

Curve Bottom Bar Download Add it to your build.gradle with: allprojects { repositories { maven { url "https://jitpack.io" } } } and: d

null 49 Sep 17, 2022
[] Port of Jake Wharton's U2020 sample app with use of MVP and Dagger 2

U+2020-mvp [DEPRECATED] We recomend to try Moxy framework instead of our solution. Port of Jake Wharton's U2020 sample app with use of MVP pattern and

Live Typing 315 Nov 14, 2022
Check out the new style for App Design aims for the Vegetable Order Service using jetpack compose...😉😀😁😎

VegetableOrderUI-Android Check out the new style for App Design aims for the Vegetable Order Service using jetpack compose... ?? ?? ?? ?? Screenshots

Shashank Singhal 330 Dec 19, 2022
A sample Android application with a strong focus on a clean architecture, automated unit and UI testing and continuous integration.

Android playground This is a sample Android application with a strong focus on a clean architecture, automated unit and UI testing and continuous inte

null 6 Jun 4, 2022
Sample application demonstrating Android design and animation

android-movies-demo This is a sample application showing off some interesting design/development interactions for a talk given at Droidcon 2013. As it

Daniel Lew 359 Jan 1, 2023