See the iOS version here: Tanga iOS
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:
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
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
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.
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
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.
Well you know what a viewModel is. Let’s not waste time here.
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.
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.
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.
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.
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.
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 🤔
- Issues should be investigated and fixed: https://github.com/rygelouv/Tanga/issues
- Set up Feature flagging system. We will probably just reuse this https://github.com/rygelouv/FeatureFlags
- Bring AI features (this is the next biggest project)
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.
- 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
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
- 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
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.