All Projects β†’ tunjid β†’ me

tunjid / me

Licence: Apache-2.0 license
A Jetpack Compose Kotlin Multiplatform WYSIWYG blog editor

Programming Languages

kotlin
9241 projects

Projects that are alternatives of or similar to me

kmm-production-sample
This is an open-source, mobile, cross-platform application built with Kotlin Multiplatform Mobile. It's a simple RSS reader, and you can download it from the App Store and Google Play. It's been designed to demonstrate how KMM can be used in real production projects.
Stars: ✭ 1,476 (+2280.65%)
Mutual labels:  multiplatform, compose, jetpack-compose
Brick
🧱 Brick - Multiplatform navigation library for Compose.
Stars: ✭ 33 (-46.77%)
Mutual labels:  multiplatform, compose, jetpack-compose
kighlighter
Simple and extendable code highlighter in Kotlin Multiplatform
Stars: ✭ 22 (-64.52%)
Mutual labels:  multiplatform, compose
SuperheroLexicon
Simple superhero lexicon app to demonstrate Jetpack Compose.
Stars: ✭ 22 (-64.52%)
Mutual labels:  compose, jetpack-compose
arkitekt
Arkitekt is a set of architectural tools based on Android Architecture Components, which gives you a solid base to implement the concise, testable and solid application.
Stars: ✭ 114 (+83.87%)
Mutual labels:  compose, jetpack-compose
Aboutlibraries
AboutLibraries automatically detects all dependencies of a project and collects their information including the license. Optionally visualising it via the provided ui components.
Stars: ✭ 2,777 (+4379.03%)
Mutual labels:  multiplatform, compose
Compose Jb
Jetpack Compose for Desktop and Web, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
Stars: ✭ 7,562 (+12096.77%)
Mutual labels:  multiplatform, compose
Delish
Delish, a Food Recipes App in Jetpack Compose and Hilt based on modern Android tech-stacks and MVI clean architecture.
Stars: ✭ 356 (+474.19%)
Mutual labels:  compose, jetpack-compose
MovieCompose
🎞 A demo movie app using Jetpack Compose and Hilt based on modern Android tech stacks.
Stars: ✭ 322 (+419.35%)
Mutual labels:  compose, jetpack-compose
PCard
Demo project to showcase adding payment card details using JetPack Compose
Stars: ✭ 61 (-1.61%)
Mutual labels:  compose, jetpack-compose
compose-backstack
Simple composable for rendering transitions between backstacks.
Stars: ✭ 411 (+562.9%)
Mutual labels:  compose, jetpack-compose
neon
Provides Jetpack Compose support for different image loading libraries.
Stars: ✭ 13 (-79.03%)
Mutual labels:  compose, jetpack-compose
ToDometer Multiplatform
WIP Kotlin Multiplatform project: A meter to-do list built with Android Jetpack, Compose UI Multiplatform, Wear Compose, SQLDelight, Koin Multiplatform, SwiftUI, Ktor Server / Client, Exposed...
Stars: ✭ 145 (+133.87%)
Mutual labels:  multiplatform, jetpack-compose
ComposeBird
Flappy Bird game
Stars: ✭ 193 (+211.29%)
Mutual labels:  compose, jetpack-compose
Composecookbook
A Collection on all Jetpack compose UI elements, Layouts, Widgets and Demo screens to see it's potential
Stars: ✭ 3,516 (+5570.97%)
Mutual labels:  compose, jetpack-compose
resaca
The right scope for View Models in Android Compose
Stars: ✭ 105 (+69.35%)
Mutual labels:  compose, jetpack-compose
compose-charts
Simple Jetpack Compose Charts for multi-platform. Including Android, Web, Desktop.
Stars: ✭ 30 (-51.61%)
Mutual labels:  compose, jetpack-compose
Jetpack-compose-sample
Forget about bunch of XML files for maintaining UIs. Jetpack Compose is Android’s modern toolkit for building native UI. Here is a small example to get started.
Stars: ✭ 29 (-53.23%)
Mutual labels:  compose, jetpack-compose
Compose-Settings
Android #JetpackCompose Settings library
Stars: ✭ 188 (+203.23%)
Mutual labels:  compose, jetpack-compose
kmp-web-wizard
Wizard for Kotlin Multiplatform
Stars: ✭ 164 (+164.52%)
Mutual labels:  multiplatform, compose

Me

Please note, this is not an official Google repository. It is a Kotlin multiplatform experiment that makes no guarantees about API stability or long term support. None of the works presented here are production tested, and should not be taken as anything more than its face value.

Introduction

"Me" is a Kotlin Multiplatform playground for ideas that pop into my head around app architecture. These ideas typically center around state, and it's production; a repository of "what ifs?". Specifically, this repository aims to explore representing various app architecture components "as state".

The app is a WYSIWYG editor for my personal website. The source for the backend can be found here.

Demo image

Some ideas explored include:

  • Mutators as abstract data types for the production and mutation of state
  • Reactive app architecture as a driver of app state
  • Android insets and IME (keyboard) behavior as state
  • Android permissions as state
  • Tiling for incremental loading (pagination) as state
  • Trees for representing app navigation as state
  • Jetpack Compose for stateful motionally intelligent global UI

πŸš¨βš οΈπŸš§πŸ‘·πŸΏβ€β™‚οΈπŸ—οΈπŸ› οΈπŸš¨

I try to keep the code at a near production quality, but this often takes a back seat to convenience and whim. I'm a huge proponent of dependency injection, yet the repository uses manual service location. Also I haven't separated my network models from my data models as well as I would like.

Again, the work presented here are the experiments of an immutable state and functional reactive programming zealot. It's far from objective, caveat emptor.

Arch

Reactive architecture

The app is a subscriber in a pub-sub liaison with the server. There is no pull to refresh, instead the app pulls diffs of ChangeListItem when the server notifies the app of changes made.

The following rules are applied to the data layer:

  • DAOs are internal to the data layer
  • DAOs expose their data with reactive types (Flow)
  • Reads from the data layer NEVER error.
  • Writes to the data layer may error and the error is bubbled back up to the caller
  • The NetworkService is internal to the data layer

Pub sub implementation

Pub sub in the app is backed by a change list invalidation based system. Its premise is:

  • Each model table on the server will have a sibling table that has a row that tracks a unique id that identifies a CRUD update (change_list_id). This unique id must have natural ordering.
  • CRUD updates to any model will cause an update for the change_list_id (akin to a new commit in git).
  • The client will then hit an endpoint asking for changes since the last change_list_id it has, or its local HEAD. A changelist of model ids that have changed will then be sent (akin to a git fetch)
  • The clients will then chew on the change list incrementally, updating its local HEAD as each update is consumed (akin to applying the pulled commits).

Real time updates are implemented with websockets via socket.io. I intend to move the android client to FCM for efficiency reasons in the future.

Navigation

Each destination in the app is represented by an AppRoute that exposes a single @Composable Render() function. The backing data structure for navigation is the tree like StackNav and MultiStackNav immutable classes. The root of the app is a MultiStackNav and navigation is controlled by a NavMutator defined as:

typealias NavMutator = Mutator<Mutation<MultiStackNav>, StateFlow<MultiStackNav>>

Global UI

The app utilizes a single bottom nav, toolbar and a shared global UI state as defined by the UiState class. This is what allows for the app to have responsive navigation while accounting for visual semantic differences between Android and desktop. Android for example uses the WindowManager API to drive it's responsiveness whereas desktop just watches it's Window size. The definition for the GlobalUiMutator is:

typealias GlobalUiMutator = Mutator<Mutation<UiState>, StateFlow<UiState>>

Pagination

Pagination is implemented as a function of the current page and grid size:

[out of bounds]                    -> Evict from memory
                                                   _
[currentPage - gridSize - gridSize]                 |
...                                                 | -> Keep pages in memory, but don't observe
[currentPage - gridSize - 1]   _                   _|                        
[currentPage - gridSize]        |
...                             |
[currentPage - 1]               |
[currentPage]                   |  -> Observe pages     
[currentPage + 1]               |
...                             |
[currentPage + gridSize]       _|                  _
[currentPage + gridSize + 1]                        |
...                                                 | -> Keep pages in memory, but don't observe
[currentPage + gridSize + 1 + gridSize]            _|

[out of bounds]                    -> Evict from memory

As the user scrolls, currentPage changes and new pages are observed to keep the UI relevant.

State restoration and process death

All types that need to be restored after process death implement the ByteSerializable interface. This allows them to de serialized compactly into a ByteArray which works excellently with Android's Parcelable type and a regular file system on Desktop. The bytes are read or written with a type called the ByteSerializer.

Things restored after process death currently include:

  • App navigation
  • The state of each AppRoute at the time of process death

Lifecycles and component scoping

Lifecycles are one of the most important concepts on Android, however Jetpack Compose itself is pretty binary; a Composable is either in composition or not. Trying to expand this simplicity to Android; the app may either be in the foreground or not.

The application state is defined in the ScaffoldModule:

class ScaffoldModule(
    appScope: CoroutineScope,
    ...
) {
    val navMutator: Mutator<Mutation<MultiStackNav>, StateFlow<MultiStackNav>> = ...
    val globalUiMutator: Mutator<Mutation<UiState>, StateFlow<UiState>> = ...
    val lifecycleMutator: Mutator<LifecycleAction, StateFlow<Lifecycle>> = ...
}

where Lifecycle is:

data class Lifecycle(
    val isInForeground: Boolean = true,
    val routeIdsToSerializedStates: Map<String, ByteArray> = mapOf()
)

With the above managing the lifecycle of components scoped to navigation destinations becomes as easy as:

internal class RouteMutatorFactory(
    appScope: CoroutineScope,
    private val scaffoldComponent: ScaffoldComponent,
    ...
) : RouteServiceLocator {
    init {
        appScope.launch {
            scaffoldComponent
                .navStateStream
                .removedRoutes()
                .collect { removedRoutes ->
                    removedRoutes.forEach { route ->
                        val holder = routeMutatorCache.remove(route)
                        holder?.scope?.cancel()
                    }
                }
        }
    }
}

By watching the changes to the available routes in the MultiStackNav class, the scopes for Mutators for routes that have been removed can be cancelled.

Each navigation destination can also be informed when the app is in the background via the isInForeground field on AppState. It's Mutator can then opt to terminate it's backing flow if necessary.

Running

As this is a multiplatform app, syntax highlighting may be broken in Android studio. You may fare better building with Intellij.

Desktop: ./gradlew :desktop:run Android: ./gradlew :android:assembleDebug or run the Android target in Android Studio

License

Copyright 2021 Google LLC

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

    https://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.
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].