Giter Site home page Giter Site logo

catalinghita8 / android-compose-mvvm-foodies Goto Github PK

View Code? Open in Web Editor NEW
446.0 8.0 84.0 30.67 MB

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

Kotlin 100.00%
android kotlin compose android-compose mvvm android-application kotlin-coroutines kotlin-flow material-ui google-material

android-compose-mvvm-foodies's Introduction

Foodies - Modern Android Architecture

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

The project tries to combine popular Android tools and to demonstrate best development practices by utilizing up to date tech-stack like Compose, Kotlin Flow and Hilt.

The sample app layers its presentation through MVVM presentation pattern. Additionally, the application features animations like expanding and collapsing row items or other effects like collapsing toolbar.

Description

Presentation patterns layers

  • View - Composable screens that consume state, apply effects and delegate events upstream.
  • ViewModel - AAC ViewModel that manages and set the state of the corresponding screen. Additionally, it intercepts UI events as callbacks and produces side-effects. The ViewModel is scoped to the lifetime of the corresponding screen composable in the backstack.
  • Model - Data source classes that retrieve content. In a Clean architecture context, one could use UseCases or Interactors that tap into repositories or data sources directly.

As the presentation layer is defined with MVVM, there are a two 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 Compose runtime MutableState object from that perfectly matches the use-case of receiving continuous updates with initial value.

  • 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 and effects.

Dependency injection

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

Most of the dependencies are injected with @Singleton scope and are provided within the FoodMenuApiProvider module.

For ViewModels, we use the out-of-the-box @HiltViewModel annotation that injects them with the scope of the navigation graph composables that represent the screens.

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 objects 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 trying to be platform agnostic as much as possible.

android-compose-mvvm-foodies's People

Contributors

catalinghita8 avatar wiryadev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

android-compose-mvvm-foodies's Issues

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.

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?

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.

[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.

[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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.