https://github.com/tunjid/heron
A graceful multiplatform bluesky client
https://github.com/tunjid/heron
architecture architecture-components bluesky-client compose compose-desktop compose-ios coroutines jetpack-datastore jetpack-lifecycle jetpack-room kotlin kotlin-multiplatform-sample kotlinx-coroutines kotlinx-datetime kotlinx-serialization pagination
Last synced: about 2 months ago
JSON representation
A graceful multiplatform bluesky client
- Host: GitHub
- URL: https://github.com/tunjid/heron
- Owner: tunjid
- License: apache-2.0
- Created: 2024-11-28T02:59:59.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2025-03-28T13:02:16.000Z (about 2 months ago)
- Last Synced: 2025-03-31T04:04:35.821Z (about 2 months ago)
- Topics: architecture, architecture-components, bluesky-client, compose, compose-desktop, compose-ios, coroutines, jetpack-datastore, jetpack-lifecycle, jetpack-room, kotlin, kotlin-multiplatform-sample, kotlinx-coroutines, kotlinx-datetime, kotlinx-serialization, pagination
- Language: Kotlin
- Homepage:
- Size: 29.3 MB
- Stars: 251
- Watchers: 2
- Forks: 9
- Open Issues: 20
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# Heron
Heron is a Jetpack Compose adaptive, reactive and offline-first bluesky client.
## Screenshots
|  |  |
|------------------------------------------------------|-------------------------------------------------------|## UI/UX and App Design
Heron uses [Material design and motion](https://m3.material.io/) and is heavily inspired by the
[Crane](https://m2.material.io/design/material-studies/crane.html) material study.Libraries:
1. Geometric shapes are created with the [Jetpack shapes](https://developer.android.com/jetpack/androidx/releases/graphics) graphics library.
2. UX patterns like pinch to zoom, drag to dismiss, collapsing headers, multipane layouts, and
and so on are implemented with the [Composables](https://github.com/tunjid/composables) library.## Architecture
This is a multi-module [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html)
project targeting Android, iOS and Desktop that follows the [Android architecture guide](https://developer.android.com/topic/architecture).For more details about this kind of architecture, take a look at the [Now in Android sample](https://github.com/android/nowinandroid) repository,
this app follows the same architecture principles it does, and the architecture decisions are very similar.There are 6 kinds of modules:
1. `data-*` is the [data layer](https://developer.android.com/topic/architecture/data-layer) of the
app containing models data and repository implementations for reading and writing that data.
Data reads should never error, while writes are queued with a `WriteQueue`.
- [Jetpack Room](https://developer.android.com/jetpack/androidx/releases/room)
is used for persisting data with SQLite.
- Given the highly relational nature of the app, a class called a `MultipleEntitySaver` is used to save bluesky
network models.
- [Jetpack DataStore](https://developer.android.com/jetpack/androidx/releases/datastore)
is used for blob storage of arbitrary data with protobufs.
- [Ktor](https://ktor.io/) is used for network connections via the
[Ozone at-proto bindings](https://github.com/christiandeange/ozone).
2. `domain-*` is the [domain layer](https://developer.android.com/topic/architecture/domain-layer)
where aggregated business logic lives. This is mostly `domain-timeline` where higher level
abstractions timeline data manipulation exists.
3. `feature-*` contains navigation destinations or screens in the app. Multiple features can
run side by side in app panes depending on the navigation configuration and device screen size.
4. `ui-*` contains standalone and reusable UI components and Jetpack Compose effects for the app
ranging from basic layout to multimedia components for displaying photos and video playback.
5. `scaffold` contains the application state in the `AppState` class, and coordinates app level
UI logic like pane display, drag to dismiss, back previews and so on. It is the entry point to
the multiplatform application
6. `/composeApp` is app module that is the fully assembled app and depends on all other modules.
It offers the entry point to the application for a platform.
It contains several subfolders:
- `commonMain` is for code that’s common for all targets.
- Other folders are for Kotlin code that will be compiled for only the platform indicated in the
folder name.
- `/iosApp` is the entry point for the iOS app.### Dependency Injection
Dependency injection is implemented with the [Kotlin Inject](https://github.com/evant/kotlin-inject) library which constructs the dependency graph at build time
and therefore is compile time safe. Assisted injection is used for feature screens to pass navigation arguments information to the feature.### Navigation
Navigation uses the [treenav experiment](https://github.com/tunjid/treeNav) to implement
Android [adaptive navigation](https://developer.android.com/develop/ui/compose/layouts/adaptive).
Specifically it uses a `ThreePane` configuration, where up to 3 navigation panes may be shown, with
one reserved for back previews and another for modals. Navigation state is also saved to disk and
persisted across app restarts.### State production
* State production follows the [Android guide to UI State Production](https://developer.android.com/topic/architecture/ui-layer/state-production).
* Each feature uses a single [Jetpack ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) as the [business logic state holder](https://developer.android.com/topic/architecture/ui-layer/stateholders).
* State is produced in a lifecycle aware way using the [Jetpack Lifecyle](https://developer.android.com/jetpack/androidx/releases/lifecycle) APIs.
* The `CoroutineScope` for each `ViewModel` is obtained from the composition's `LocalLifecycleOwner`
* The specifics of producing state over time is implemented with the [Mutator library](https://github.com/tunjid/Mutator).
* Inputs to the state production pipeline are passed to the mutator in the `inputs` argument, or derived from an action in `actionTransform`.
* Every coroutine launched is limited to running when the lifecycle of the component displaying it is resumed. When the lifecyle
is paused, the coroutines are cancelled after 2 seconds: `SharingStarted.WhileSubscribed(FeatureWhileSubscribed)`.
* Each user `Action` is in a sealed hierarchy, and action parallelism is defined by the `Action.key`. Actions with different keys run in parallel
while those in the same key are processed sequentially. Each distinct subtype of an `Action` hierarchy typically has it's own
key unless sequential processing is required for example:
* All subtypes of `Action.Navigation` typically share the same key.
* All subtypes of pagination actions, also share the same key and are processed with the [Tiling library](https://github.com/tunjid/Tiler).