Giter Club home page Giter Club logo

superheroesandroid's Introduction

Superheroes App

A sample project using the Marvel API to show a list of superheroes and some stats about them.

Superheroes List Superhero Details Error & Retry
Superheroes List Superhero Details Error Loading

Marvel API KEY

The project need marvel_public_api_key and marvel_private_api_key to build. You can add them to your home level gradle.properties file (located in ~/.gradle on Unix based systems):

marvel_public_api_key=<PUBLIC API KEY HERE>
marvel_private_api_key=<PRIVATE API KEY HERE>

or using -Pmarvel_public_api_key=<PUBLIC API KEY HERE> -Pmarvel_private_api_key=<PRIVATE API KEY HERE> to each gradle command e.g.:

./gradlew assembleDebug -Pmarvel_public_api_key=<PUBLIC API KEY HERE> -Pmarvel_private_api_key=<PRIVATE API KEY HERE>

Check out the Marvel Developer portal for more info.

App Architecture

The app uses a reactive architecture built atop Flow. The app follows a layered architecture with data, domain and presentation layer. The package structure is an attempt to package by feature, however both screens share the data and domain layers. The app uses a single activity + fragments and the Jetpack Navigation Component.

Data Layer

The API call is modeled using Retrofit, KotlinX Serialization as the converter. The data layer converts the DTO objects to Domain objects. Any expected errors that happen up to this point are mapped to a sealed class SuperheroError. A custom exception SuperheroException that contains a property of the error is delivered as an Flow error in the stream.

Domain Layer

The main class here is Superhero. It has a static create function that converts the string that comes from the API (as a thumbnail) into a HttpUrl also making sure it's https (so it works on Android).

Presentation Layer

Each screen is represented by a Fragment which plays the role of glue code. It's responsible for DI, forwarding actions from the view (Compose) to the ViewModel and forwarding state from the ViewModel to the view. It also handles any side effect emitted by the ViewModel e.g. navigation events.

Each fragment has a Jetpack ViewModel that:

  • exposes a single Flow<ViewState> backed by a MutableStateFlow (caching the last item) describing the state of the view at a given time
  • exposes a single Flow<Effect> backed by a Channel for side effects like navigation, Snackbar or similar. Event that happen when no-one is subscribed are cached. All events are delivered when subscribed
  • exposes a CoroutineScope with operations tied to it's lifecycle

The Fragment observes the Flow<ViewState> between onStart and onStop and updates the Sceen. The Fragment observes Flow<Effect> between onStart and onStop making sure fragment transactions are executed only when the view is active.

The Fragment observes the Flow<Action> from onStart until onStop. However any network calls that result from those interactions are de-coupled from this lifecycle. The operations triggered by the view actions are coupled to the ViewModel lifecycle and are only disposed in the ViewMode.onDispose() function. Check the fork() function for more details.

The logic is written as extension functions on top of a module (collection of dependencies).

Dependency Injection

The sample uses the DI approach from Simple Kotlin DI. The dependencies with Singleton scope live in the app as AppModule. Each fragment uses the AppModule dependencies and can add it's own (e.g. the ViewModel) that are un-scoped or use Jetpack for scoping (e.g. ViewModel).

The logic is written as extension functions on top of a module (collection of dependencies).

Testing

This sample uses kotest as a testing library. The presentation logic is tested by mocking the Retrofit Service and using a TestViewModel that uses MutableSharedFlow instead of MutableStateFlow and remembers all events. Tests use the real schedulers and Turbine for testing Flow.

The view is tested in isolation using Espresso, by setting a ViewState and verifying the correct elements are displayed/hidden and the text matches the expected.

There is also one E2E (black box) test using Maestro that tests both fragments + activity together.

Acknowledgments

Approaches is this sample are heavily inspired by open source code I have read. It is impossible to list them all, but two samples that were key are:

superheroesandroid's People

Contributors

dependabot[bot] avatar lordraydenmk avatar renovate[bot] avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar

superheroesandroid's Issues

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Location: renovate.json
Error type: Invalid JSON (parsing failed)
Message: Syntax error: expecting end of expression or separator near "grou

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

  • Update Update Androidx (androidx.fragment:fragment-ktx, androidx.lifecycle:lifecycle-runtime-compose, androidx.lifecycle:lifecycle-viewmodel-ktx, androidx.appcompat:appcompat, androidx.compose.material:material, androidx.compose.foundation:foundation, androidx.compose.ui:ui-test-junit4, androidx.compose.ui:ui-test-manifest, androidx.compose.ui:ui-tooling, androidx.compose.ui:ui)
  • Update Update other dependencies (gradle, io.kotest:kotest-assertions-core-jvm, io.kotest:kotest-runner-junit5-jvm, app.cash.paparazzi, com.android.application)
  • Update kotlin (org.jetbrains.kotlinx:kotlinx-serialization-json, org.jetbrains.kotlinx:kotlinx-coroutines-android, androidx.compose.compiler:compiler, composeOptions, org.jetbrains.kotlin.plugin.serialization, org.jetbrains.kotlin.android)
  • Update kotlin to v2 (major) (org.jetbrains.kotlin.plugin.serialization, org.jetbrains.kotlin.android)
  • Click on this checkbox to rebase all open PRs at once

Detected dependencies

github-actions
.github/workflows/ci.yml
  • actions/checkout v4
  • actions/setup-java v4
  • gradle/gradle-build-action v3
  • actions/checkout v4
  • actions/setup-java v4
  • gradle/gradle-build-action v3
  • actions/cache v4
  • reactivecircus/android-emulator-runner v2
  • reactivecircus/android-emulator-runner v2
  • actions/upload-artifact v4
gradle
gradle.properties
settings.gradle
build.gradle
  • com.android.application 8.4.0
  • org.jetbrains.kotlin.android 1.9.23
  • org.jetbrains.kotlin.plugin.serialization 1.9.23
  • app.cash.paparazzi 1.3.3
app/build.gradle
  • composeOptions 1.5.12
  • org.jetbrains.kotlinx:kotlinx-coroutines-android 1.8.0
  • androidx.compose.compiler:compiler 1.5.12
  • androidx.core:core-ktx 1.13.1
  • androidx.appcompat:appcompat 1.6.1
  • androidx.activity:activity-compose 1.9.0
  • androidx.compose.ui:ui 1.6.7
  • androidx.compose.foundation:foundation 1.6.7
  • androidx.compose.material:material 1.6.7
  • androidx.compose.ui:ui-tooling 1.6.7
  • androidx.appcompat:appcompat 1.6.1
  • com.google.android.material:material 1.12.0
  • androidx.lifecycle:lifecycle-viewmodel-ktx 2.7.0
  • androidx.lifecycle:lifecycle-runtime-compose 2.7.0
  • androidx.fragment:fragment-ktx 1.7.0
  • androidx.navigation:navigation-fragment-ktx 2.7.7
  • androidx.navigation:navigation-ui-ktx 2.7.7
  • com.jakewharton.timber:timber 5.0.1
  • io.coil-kt:coil-compose 2.6.0
  • com.squareup.retrofit2:retrofit 2.11.0
  • com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter 1.0.0
  • org.jetbrains.kotlinx:kotlinx-serialization-json 1.6.3
  • com.squareup.okhttp3:logging-interceptor 4.12.0
  • androidx.compose.ui:ui-test-manifest 1.6.7
  • io.kotest:kotest-runner-junit5-jvm 5.9.0
  • io.kotest:kotest-assertions-core-jvm 5.9.0
  • app.cash.turbine:turbine 1.1.0
  • org.junit.vintage:junit-vintage-engine 5.10.2
  • io.coil-kt:coil-test 2.6.0
  • androidx.test.ext:junit 1.1.5
  • androidx.test.espresso:espresso-core 3.5.1
  • androidx.test.espresso:espresso-contrib 3.5.1
  • com.squareup.okhttp3:mockwebserver 4.12.0
  • androidx.compose.ui:ui-test-junit4 1.6.7
gradle-wrapper
gradle/wrapper/gradle-wrapper.properties
  • gradle 8.7

  • Check this box to trigger a request for Renovate to run again on this repository

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.