2022 Profolio Project: multi-module Jetpack Compose app showcasing The Met's impressionist collection.
Built with The Metropolitan Museum of Art Collection API.
| ShowcaseScreen | PaintingScreen |
|---|---|
![]() |
![]() |
Follows a Redux-style architecture where the Result<T> of an API call of is reduced into a UIState<T>. A generic Composable fun <T: UIState> StatefulScreen() renders the UIState into success, loading, or error UI.
A ViewModel implements the ScreenViewModel<T: UIState> and Reducer<T: UIState, Result<D: Any>> interfaces to define the state management API's that emit an immutable StateFlow<UIState> on any change to the underlying data source.
Following the Android Guide to Modularization, the implementation is modularized across app, feature, infrastructure, and core modules. The core modules define low-level API and UIState data models. The infrastructure modules provide reusable platform tools. User-facing Screen destinations are in feature modules and are implemented with a reusable infrastructure:ui_component library. The app module contains the MainActivity and coordinates the Compose NavGraph between Screens. The infrastructure and core modules are suitable for Kotlin multiplatform.
- app
- MainActivity
- App.kt: the App Composable wraps the
Scaffoldto swap out Screen Composables when navigating between destinations, set the toolbar, bottom Nav, etc. - navigation: the Compose
NavHostimplementation for this app
- feature
- met
- showcase: the ShowcaseScreen, ShowcaseUIState, and ShowcaseViewModel
- painting: the PaintingScreen, PaintingUIState, and PaintingViewModel
- met
- infrastructure
- image: an AsyncImageComponent(url) with a Coil Image implementation
- reducer: a Reducer that transforms an old
UIStateandResult<D: Any>, into a new UIState - router: a Router that implements navigation between Compose Screen Route's
- screen: generic Screen Composables for LoadingScreen, ErrorScreen, EmptyScreen, and <T: UIState> StatefulScreen to render UI based on a
UIState - viewmodel: a ScreenViewModel interface that is implemented to render the
state: StateFlow<T: UIState>in a <T: UIState> StatefulScreen - met
- met_network: the MetRepository implementation to fetch remote data from The Met Collection API or return local data from the database.
- met_route: the
Routeimplementation for this app: i.e., the top-level MetRoute Screen destinations forMetRoute.ShowcaseandMetRoute.Painting - met_ui_component: the "Met UI Component library" that can be used across feature models
- core
- Material3 Theme
- Screen
- Card
- Component
- Material3 Design Token
| UI Hierarchy | State Holder | State |
|---|---|---|
| Screen | Android ViewModel | StateFlow<UIState> |
| Card | Stateless or Compose State | StateFlow<T> |
| Component | Stateless or Compose State | StateFlow<T> |
Guidance on Android vs Compose API's found in State in Compose documentation.
The met_network module contains unit tests for the MetRepository. There are two implementations for example purposes:
- MetRepositoryImplTestWithMockWebServer
- A MetRepositoryImpl test that uses Square's MockWebServer in a MetNetworkClient to drive test network responses with JSON files in the resources folder. A FakeMetDatabase instance can set
FakeMetDatabase.setIsSuccessto return a mocked successful or failure response.
- A MetRepositoryImpl test that uses Square's MockWebServer in a MetNetworkClient to drive test network responses with JSON files in the resources folder. A FakeMetDatabase instance can set
- MetRepositoryImplTestWithMockk
- A MetRepositoryImpl test that uses Mockk to mock MetNetworkClient and MetDatabase instances and their functions.
The Metropolitan Museum of Art Collection API is more of an academic resource than it is a production-ready API suitable for news feeds at scale. It can take 30-45 seconds for a list of our 70-ish MetObject's - the data class representing a painting - to return.
From a backend perspective, the response time could possibly be improved by (1) one batch collection API call that sends a list of ids as a query parameter, (2) a MetObjectMetadata type with a few top-level fields for the work's image link, title, and artist that could drive a Card UI. These would eliminate the need for 70 sequential one-shot MetObject API calls to the `/objects/[objectID]/ endpoint and reduce the time it takes to download the data for a list feed, respectively.
Since we cache the results of the first API call in a Room SQL database, only the first launch to seed the database takes extra time and the user is informed with a loading screen message. For a portfolio sample app that's OK.

