Skip to content
/ Tanga Public

An Open Source Book Summary App. The simplest, most focused way to experience life-changing book summaries.

License

Notifications You must be signed in to change notification settings

rygelouv/Tanga

Repository files navigation

Tanga Android App

See the iOS version here: Tanga iOS


API License API API API


store_banner.png


License Playstore button Website button


We are in production 🎉🎉

Here is our latest major additions:

  • feat: Add release build workflow and update dev build workflow by @rygelouv in #119
  • Added support for Push notifications by @rygelouv in #120
  • Fixed and improved Mini player visibility behaviour and added ui improvements by @rygelouv in #123
  • Play audio in background by @rygelouv in #110
  • Add mini player by @rygelouv in #112
  • Allow anonymous user to access weekly summary by @rygelouv in #113

Check out the releases section: https://github.com/rygelouv/Tanga/releases

The other projects can be found here:

Basic code organization

Feature code is located in the feature package. Each feature package follows a specific code organization. Each feature has the following components:

  • Container
  • Navigation
  • Screen
  • View Model
  • UI Contract

big_diagram_02.png

Containers

Each feature use a Container which role is to wrap the UI screen and make it independent of external and complex dependencies such as viewModels, navigation components etc.

This Allow us to have Screens that are pure Compose composables with their state properly hoisted (I think) not depending on any ViewModel or NavController. This means the Ui screens can easily be reused (say in Compose KMP for example) and Previews become easier to set up.

All of that is possible thanks to the concept of containers composables. A Container does:

  • Get an instance of a ViewModel
  • Observe the ViewModel state and events
  • Pass the UI state object to the Screen
  • Handle UI Events for navigation actions
  • Call actual viewModel functions that are wrapped as lambda before being passed down to the Screen

small_diagram_02.png

Screens

As said in the Containers section. Screen must be free from viewModels and navigation as much as possible. Screens should simply receive the UI state and pass down the necessary state elements to its child composables. This is to ensure a proper state hoisting.

Navigation files

Each feature should have its own navigation file that contains the code and logic on how we navigate to the feature. Navigation file essentially contain navigation composables or graphs that take to the Screen Containers

UI Contract

Let’s make it straight, there is no component in Tanga called a “UI Contract”. This is simply the name of the file that holds the definition of UI state and UI events for a specific feature. This could have been in the ViewModel or somewhere else, but we have decided to put in a separate file called a UI Contract. By “Contract” you can understand the UI contract that binds the UI Screen to the ViewModel. Inspired from an old concept from the old MVP days.

ViewModel

Well you know what a viewModel is. Let’s not waste time here.

Architecture?

This project follows a typical/classic MVVM approach or whatever you may wanna call it if you think it’s not MVVM, it doesn’t really matter. What is important here is that we use a ViewModel class that holds an observable state object. State must be modeled in the form of a single object that represents all its variations. We use data class to model and represent state. It could have been sealed classes but that also doesn’t really matter, each approach has their pros and cons.

Why isn’t the Repository also in the feature package

There is a misconception among Android engineers who tend to attach and couple a repository to a feature. They will build a UI screen, then it’s viewModel and then its repository and sometime even adding a useless UseCase in the middle. A repository should NOT be coupled to a feature. A repository should be setup by Model and not by feature. For example: UserRepository manages data related to the User model, OrderRepository managers data related to Orders (CRUD and all other type of operations). HomeRepository or SearchRepository or ReadSummaryRepository are not valid repositories. So a repository has nothing to do in a feature package. Plus, a repository should be reusable across multiple features. Even if you had UseCases, repositories and UseCase are not the same type of components in your architecture. You can have a UseCase for each feature but you should not have Repo for each feature. And seriously, think twice before using UseCases though.

Interactors?

UseCases are supposed to be components that help isolate business logic and eventually reuse them as well as allowing the testability of such logic. However UseCases in the Android community has become a form of weird architectural pornography fantasy. We don’t use UseCases on the Tanga project. But we do think that sometimes a mobile app needs to run some business logic or may just need to “massage” some data from the repo/data source before passing it down to the ViewModel. That is where we bring Interactors in. They are components of business logic but:

  • They are not always needed in every feature. Only when necessary and where they actually make sense.
  • They don’t just stupidly contain a single line function that calls the Repo. They have multiple functions that serve multiple different operations and logic for the same feature.
  • When they are present that means they actually do some real logic and not just delegating calls.
  • Think of them as putting multiple (actual) UseCases together in a single class.

Module Organization

Tanga project doesn’t have a module per feature breakdown as you’ll tend to see in many android sample projects that fantasize on modules. There is no plan of having a Module per feature breakdown because:

  • Some features are very small, why have a full feature module with everything that it implies (gradle, navigation trouble, communication with other modules etc…) for just a single screen?
  • More feature modules means more challenges in terms of navigation, communication between those, DI etc. Is it really worth it?
  • Breaking down in module should also depend on the team and the size of the project. Tanga is built by a single developer and is a relatively small project why bother with adding modularization complexity?

We only put in modules those parts we think actually make sense (tracking, ui system) and are not necessarily feature related. All the feature work sits in the app module.

Core-ui module

This module contains our design system and all the core element needed to build our UI

What’s wrong and/or what is missing?

The design system is still very poor. Some components such as texts and some other buttons used in app module, should have been part of the design system in core-ui.

We need to find all UI components that must be moved to this module and move them.

Tracking Module

Contains code for everything related to tracking.

  • Analytics
  • Errors
  • Performances

What’s wrong and/or what is missing?

At the moment we are only tracking analytics so far. We need to decouple error tracking from app module and move it to Tracking module. We also need to add performance tracking and tracing in this module. We may not need an abstraction for Analytics Providers though? Not sure 🤔

Next important projects


Infrastructure

Tanga relies almost entirely on Firebase for its infrastructure. We use Firebase for:

  • Authentication
  • Firestore Database
  • Analytics
  • Crashlytics Error Tracking
  • Remote Config for feature flags
  • Performance Monitoring
  • Messaging for push notifications
  • Storage for images and other files such as audio and graphics

We also use Sentry for extra error tracking and monitoring. We use RevenueCat for in-app purchases and subscriptions.

tanga_infra_02.png

Automation Infrastructure work

  • Add Bitrise for CI*. We removed it because it was too expensive for a single developer. We are now using Github Actions
  • Add Github Actions for CI
  • Add Ktlint
  • Add Detekt
  • Add SonarCloud
  • Add Codecov for test coverage tracking
  • Add error tracking system with Sentry and Crashlytics
  • Add Detekt Step to CI
  • Add full Android build on Github Action workflow

Testing

Some effort is put in testing. We've added some good amount of unit tests and UI tests. UI tests must be broken at the moment. We plan on getting rid of them enterily.

  • Add JUnit 5
  • Add Mockk
  • Add Codecov for tracking project coverage
  • Add Kover and Jacoco for generating coverage reports - We've mixed this both and need to improve things around here
  • Start adding unit tests.
  • UI test the screen composables - We've stopped adding UI tests for screens and will focus only on some components when necessary.
  • Reach 50% coverage - We only test the important components such as viewModels and interactors and sometimes repositories. Not that coverage really matters but it's 50% is a pretty nice number
  • Add Screenshot tests
  • Add Maestro tests

Performances

  • On debug builds, the app is very slow on physical devices. This is need to be investigated. We started here: #92
  • Add macrobenchmark for Home screen
  • Add baselie profiles if necessary
  • Enable StrictMode to make sure no blocking work is done on the UI thread
  • Track Memory leaks with Leak canary

License

 Copyright 2023 Rygel Louv

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.