Skip to content

Android app for integrating gratitude into your routine

Notifications You must be signed in to change notification settings

susumunoda/kansha-android

Repository files navigation

Summary

In Japanese, kansha (感謝) means gratitude. This app aims to be a way to increase gratitude in the user's life by providing the following functionality:

  1. [MVP] Allow users to write notes in different categories for things that they are grateful for in life
  2. Allow users to schedule reminders to take a moment to be grateful and, if they want to, write a note
  3. Allow users to set and track goals for how often they want to integrate gratitude into their routines

Platform

For the time being, this project is only being built for Android. An iOS version may be added in the future, but it is not currently planned.

Modularization

Where possible, logic that is not specific to this app and that could possibly be useful in other apps has been extracted into separate libraries (i.e. GitHub repos). For example, the authcontroller library contains app-agnostic logic for managing authentication. As another example, this app uses Firebase's Cloud Firestore for its backend, and the android-firebase-firestore library provides a useful abstraction over the barebones Android Firestore SDK.

Additionally, a large benefit of not tightly coupling the app with implementation-specific details of its components (e.g. by interacting with only interfaces throughout the codebase) is that changes to the internal implementation details will result in minimal to no changes in the rest of the application. For example, the firebase-auth library was updated to support Kotlin Multiplatform — meaning its internals changed to rely on a completely different, KMP-compatible Firebase SDK — but the changes in this repository were little more than updating package names and making a small tweak due to a minor API change.

Implementation

UI

The UI for this app is built using a single-activity architecture with Jetpack Compose. To keep the code modular, each screen generally consists of a composable function that receives a view model (injected with Hilt), and the view model typically maintains state which is an instance of a data class for ease of copying during state updates. See AuthScreen, AuthScreenViewModel, and AuthScreenState for examples.

Navigation

Navigation is implemented with Navigation Compose. In addition, a custom compose-navigation library includes useful utilities such as a bottom navigation bar with sensible default behavior.

Currently, the app has two top-level NavHosts, one for an authenticated (logged in) state and the other for an unauthenticated (logged out) state. The main reason to do this was that the UI scaffolding differed substantially between the authenticated and unauthenticated states (e.g. no bottom nav bar when logged out) and that, currently, there is no intention to support navigating to a specific authenticated screen directly after being authenticated. This may change as features evolve.

In the authenticated flow, all navigation uses the same top-level NavHost, including nested navigation which is achieved with the NavGraphBuilder.navigation extension function. See SettingsNavigation for an example.

Authentication

Authentication is implemented with Firebase Authentication. Session and user state is handled by a custom authcontroller library that provides an AuthController interface for observing changes to the session state via its sessionFlow property; this is useful for observing the current session/user in view models and in composable functions. In addition, the SessionListenerHandler class provides a hook for performing an action on user login/logout.

Networking

In its current state, the app does not make raw network requests (e.g. with Ktor or Retrofit) and instead relies on SDKs to make requests under the hood — for example, Firebase for auth and database queries and Coil for loading images.

Persistence

Currently, this app requires a network connection as data is persisted using Firebase's Cloud Firestore. A decision was made fairly early to switch from Firebase Realtime Database to Firestore for its more structured schemas and advanced querying capabilities.

Dependency Injection

Dependency injection is handled by Hilt with kapt for Java annotation processing. Note: It is an item on the backlog to migrate from kapt to KSP as KSP support for Dagger/Hilt was introduced in alpha state in Dagger 2.48.

Asynchronous Programming

This project makes heavy use of Kotlin coroutines for handling asynchronous code. In addition, Kotlin flows are used heavily for observing changes to data over time, such as emitting state changes in view models and observing the changed state in a composable function, or observing a stream of realtime data over the network.

Progress

As of now, the MVP of allowing users to create categories and notes is mostly complete. Still missing are the abilities to edit and delete categories and notes.

See open issues for a complete list of remaining work items.

Screenshots

Screenshot 2023-10-17 at 11 52 26 AM Screenshot 2023-10-17 at 11 49 30 AM Screenshot 2023-10-17 at 11 52 04 AM Screenshot 2023-10-17 at 11 57 49 AM Screenshot 2023-10-17 at 11 59 29 AM

Demos

Demo.10-17-2023.480p.mov