A thought experiment on architecture, object-oriented programming, and composability.

Overview

Journal3

CI Status Badge

There's barely anything special about the features that Journal3 is offering, it's literally yet another journaling application.

What is special about it though (or at least what I think is special about it) is that at the same time it's also a thought experiment; an avenue to explore the what-ifs.

What if there's a way to code an Android application without any architecture? Relying instead on the good ol' software engineering patterns and object-oriented principles?

What if there's a way to bring the power and flexibility of declarative programming to the domain?

What if we treat objects with respect instead of merely thinking of them as dumb containers of data?

Structure

I do not intend to apply any kind of architecture to Journal3 nor am I trying to enforce any patterns within it. However, there is a common structure that you would come to realize when diving deeper into the codebase.

  • UseCase as representation of business requirements/features
  • Event as the dominant way of inter-objects communication
  • Router as the primary enabler for navigation
  • Presenter as the renderer of objects (not to be confused with MVP)

One thing that I hope you'd also notice is that they are not necessarily architecture components, rather merely a byproduct of encapsulation.

Highlights

Contributing

Interested in joining this journey with me? Feel free to open up issues to start a discussion on anything related to Journal3. I also accept any kind of pull-requests that would help push this journey forward.

License

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
Comments
  • BackgroundExecutingPresenter

    BackgroundExecutingPresenter

    https://github.com/MrHadiSatrio/Journal3/blob/30ff779807a5e171d9b86fb0d8e463836fa27039/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/story/ViewStoriesActivity.kt#L52

    Could we do the same as BackgroundExecutingUseCase for the Presenter?

    enhancement 
    opened by rhmndnt 4
  • Why does BackgroundExecutingUseCase expect client to give Dispatcher.Default?

    Why does BackgroundExecutingUseCase expect client to give Dispatcher.Default?

    https://github.com/MrHadiSatrio/Journal3/blob/30ff779807a5e171d9b86fb0d8e463836fa27039/app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/story/ViewStoriesActivity.kt#L48

    This decorator's name suggests something like this:

    class BackgroundExecutingUseCase(
        private val coroutineScope: CoroutineScope,
        private val origin: UseCase
    ) : UseCase {
    
        override fun invoke() {
            coroutineScope.launch(Dispatchers.Default) { origin() }
        }
    }
    
    enhancement 
    opened by rhmndnt 3
  • Use-cases are not properly cancelled on activity destroy events

    Use-cases are not properly cancelled on activity destroy events

    So far we've only been handling user-sourced cancellation events (through BackButtonCancellationEventSource). We should also channel such events on appropriate lifecycle state change (e.g., Activity#onDestroy()) lest we'd end up with leaks.

    An implementation of it could look like so:

    class LifecycleCancellationEventSource(
        private val lifecycle: Lifecycle
    ): EventSource {
    
        private val events: Flow<CancellationEvent> by lazy {
            callbackFlow {
                val observer = LifecycleEventObserver { source, event ->
                    if (event != Lifecycle.Event.ON_DESTROY) return@LifecycleEventObserver
                    trySend(CancellationEvent("system"))
                }
                lifecycle.addObserver(observer)
                awaitClose { lifecycle.removeObserver(observer) }
            }
        }
    
        constructor(activity: ComponentActivity) : this(activity.lifecycle)
    
        override fun events(): Flow<Event> {
            return events
        }
    }
    

    Post that, it's only a matter of ensuring all use-case declaration would have that as one of their event sources.

    bug good first issue 
    opened by MrHadiSatrio 2
  • Get rid of Router

    Get rid of Router

    What has changed

    Router has been deprecated since #14. This PR will completely remove it from the codebase as well as README. Its replacement comes in two forms:

    1. ActivityRoutingEventSink (which has been expanded to cover more Event types) for forward navigations, and
    2. ActivityCompletionEventSink for backward navigations

    Why was it changed

    I believe ActivityRoutingEventSink plus ActivityCompletionEventSink provides a more idiomatic way to trigger navigation. It can be supplied as part of the global sinks which alleviate UseCases from having to explicitly depend on a dedicated routing object.

    opened by MrHadiSatrio 1
  • Support geographical tagging

    Support geographical tagging

    What has changed

    It is now possible to tag geographical data (places) onto moments. In order to facilitate this feature, a number of things has to be added/changed.

    Deprecation of Router

    This is the first time we ever had to model cross-Activity interaction (see SelectAPlaceActivity) and it made me realize the flaw in Router-based navigation. While it's true that it allows use-cases to direct the flow of screens, at the same time it also invite the assumption of a structure; of a multi-screen application in this case.

    Another problem comes from the fact that with Router modeling activity "callbacks" felt clunky because of its fire-and-forget nature.

    The current approach I'm taking now is to deprecate Router in favor of event-driven navigation. Use-cases should simply sink events that are outside of their scope. At the same time, a global EventSink would react to such events and navigate to a proper component (i.e., activity). Meanwhile, the "callback" aspect of it relies on the fact that all use-cases would have the global EventHub as one of their EventSources. Meaning they can "resume" their work basing on the same event.

    This approach alleviates the need of coupling the event stream from the inherently Android-specific concepts of activity navigation.

    Deprecation of Coroutines

    I've been met with a lot of inconsistencies that seemed to come from our Coroutines-backed decorators. I didn't really spend time to dig in deep, rather tried to implement alternative decorators backed by Executors instead. And the problem disappeared. (Maybe #5 has something to do with it.)

    Probably will revisit the "why" later on. But for now, it works. And because we don't actually need a lot of features Coroutines offer, I don't see any harm of going ahead with Executors for now.

    Handling of CancellationEvent within use-cases

    This is a bug that have been there for a while (as raised in #13) but the introduction of Executors made it more apparent. As we're setting a conservative limit on the number of threads (via Runtime.getRuntime().availableProcessors()) these zombie use-case instances literally blocked the app from functioning after a short period of usage.

    The solution for now comes in the form of LifecycleTriggeredEventSource, set to emit CancellationEvent upon Lifecycle.Event.ON_DESTROY. Plus change in use-case classes to ensure the event is handled properly.

    CurrentActivity behavior

    In its original form CurrentActivity would throw an exception when there are no "current" activities. However, I don't feel like it paints the correct picture. When you're interested in knowing something that is "current", would you stop looking when it's not there yet?

    The behavior now is changed to simply block the calling thread when no such activities are there. I believe this is fine as we're running close to everything on background threads. Another plus side is that this would allow downstream requirements (e.g., like the one added within PermissionAwareCoordinates) to behave correctly even in edge scenarios.

    Adoption of Ktor

    Nothing to elaborate on this, really. This project is inherently a KMM one, so Ktor is our best bet for facilitating network I/O.

    Why was it changed

    To see how this project structure would fare when pitted against network I/O (HERE API), sensor-reading (GPS, on Android through LocationManager), and Runtime Permission requirements.

    opened by MrHadiSatrio 1
  • Integrate SonarCloud into the project

    Integrate SonarCloud into the project

    What has changed

    This project is now configured to be analyzed by SonarCloud.

    Why was it changed

    To ensure that the quality of this project won't degrade overtime.

    opened by MrHadiSatrio 1
  • Sort moments on screen according to their timestamp

    Sort moments on screen according to their timestamp

    What has changed

    Moments shown in the story detail screen are now sorted, in descending order, according to their timestamp.

    Why was it changed

    To make it easier to read back written moments from the past. Unless they are sorted in some way, the user won't be able to make sense of the timeline in which their moments took place.

    opened by MrHadiSatrio 1
  • Fix handling of list item click events

    Fix handling of list item click events

    What has changed

    In RecyclerViewItemSelectionEventSource, ACTION_MOVE in between ACTION_DOWN and ACTION_UP would no longer prevent a click event from being emitted.

    Why was it changed

    On a real device, it's unlikely that ACTION_DOWN would be immediately followed by ACTION_UP; there's almost always a small ACTION_MOVE in between. This behavior is reproducible almost every time on a Google Pixel 5 running Android 13.

    opened by MrHadiSatrio 0
  • Remind users to write after a certain duration

    Remind users to write after a certain duration

    What has changed

    Added a new use-case to alert users if they haven't recorded any moments after a certain duration had passed (currently set to 3 hours). On the Android app, this use-case would be run on top of the WorkManager API and would talk to a notification-backed presenter; resulting in a periodical notification setup.

    Why was it changed

    Because I need this feature to help keep my journaling habit in check.

    Related issues

    • N/A
    opened by MrHadiSatrio 0
  • Add new domains around story and moments

    Add new domains around story and moments

    What has changed

    Added two new domains: One to show individual stories (including their moments) and another one to edit moments.

    Why was it changed

    Because otherwise users won't be able to write down their thoughts and use the app as intended.

    Related issues

    • N/A
    opened by MrHadiSatrio 0
  • Fix handling of illegal JSON characters during (de)serialization

    Fix handling of illegal JSON characters during (de)serialization

    What has changed

    We're now depending on JsonPrimitive#content to obtain raw string values within JsonFile.

    Why was it changed

    JsonElement#toString() will attempt to surround content with ", which we don't want. Simply removing those surrounding quotes manually is error-prone.

    opened by MrHadiSatrio 0
  • SonarCloud is not reporting correct coverage

    SonarCloud is not reporting correct coverage

    In https://github.com/MrHadiSatrio/Journal3/issues/14#issuecomment-1345552827, Sonar reported 0% coverage for all the newly-added code, which is not true (see https://github.com/MrHadiSatrio/Journal3/pull/14/commits/da2090b499b9ef6f472154e4aa3813e8908f2dc8).

    Most probably this is a configuration error. We need to take a second look at the setup and get this fixed.

    bug 
    opened by MrHadiSatrio 0
  • runBlocking

    runBlocking

    https://github.com/MrHadiSatrio/Journal3/blob/30ff779807a5e171d9b86fb0d8e463836fa27039/app-kmm-journal3/src/commonMain/kotlin/com/hadisatrio/apps/kotlin/journal3/story/ShowStoriesUseCase.kt#L50

    Allow me to query more detail about the usage reason of runBlocking here. AFAIK, the official documentation only encourages runBlocking in main functions and tests. It could be dangerous if we run this usecase on a single thread dispatcher like Dispatchers.Main.

    The same document also mentions "should not be used from a coroutine", meanwhile we decorate ShowStoriesUseCase using BackgroundExecutingUsecase that internally launches a coroutine.

    enhancement 
    opened by rhmndnt 1
  • UseCase == God Object

    UseCase == God Object

    As you have stated that UseCase is a representation of business requirements/features, is it necessary that we should only have 1-to-1 mapping between Activity/Screen/Feature and UseCase?

    question 
    opened by rhmndnt 1
  • PURE Architecture

    PURE Architecture

    I know you've clearly stated that you don't intend to apply any kind of architecture to Journal3, but if you do "PURE" would be a great name for this ­čśä Presenter-UseCase-Router-Event.

    documentation 
    opened by rhmndnt 1
Owner
Hadi Satrio
Android developer at Gojek.
Hadi Satrio
Ivy FRP is a Functional Reactive Programming framework for declarative-style programming for Android

FRP (Functional Reactive Programming) framework for declarative-style programming for Andorid. :rocket: (compatible with Jetpack Compose)

null 8 Nov 24, 2022
Minecraft 1.18.2 Backport of Petal, a performance-oriented fork of Purpur intended to increase performance for entity-heavy servers by implementing multi-threaded and asynchronous improvements.

Sakura Performance Minecraft JAR Sakura is a performance-oriented fork of Purpur intended to increase performance for entity-heavy servers by implemen

etil.sol 14 Nov 23, 2022
A repo to experiment with Continuation R, A implementations in Kotlin

A repo to experiment with Continuation R, A implementations in Kotlin

Simon Vergauwen 12 May 15, 2022
ShapeShift´ŞĆ - A Kotlin library for intelligent object mapping and conversion between objects

ShapeShift´ŞĆ A Kotlin library for intelligent object mapping and conversion between objects. Documentation Installation Maven Gradle Groovy DSL Kotlin

KRUD 127 Dec 7, 2022
Kotlin Object Notation - Lightweight DSL to build fluid JSON trees

Kotlin Object Notation Lightweight kotlin MPP DSL for building JSON trees Setup Just drop the dependency in your commonMain sourceSet kotlin { sourc

Martynas Petuška 43 Dec 10, 2022
Kotlin Android object for global applicationContext

ContextProvider Kotlin Android object for global applicationContext Usage In your Aplication class class YourApp : Application() { override fun o

PaulRB 0 Nov 4, 2021
EVMapper: Simple object mapper for Kotlin

EVMapper - Simple object mapper for Kotlin Simple mapping data class AData(val p1: Int, val p2: Int) data class BData(val p1: Int, val p2: Int) val a

Igor 8 Jan 24, 2022
A modular object storage framework for Kotlin multiplatform projects.

ObjectStore A modular object storage framework for Kotlin multiplatform projects. Usage ObjectStore provides a simple key/value storage interface whic

Drew Carlson 4 Nov 10, 2022
A template that utilizes both Scala and Kotlin because why not (And also because I endorse programming hell)

Fabric-Scala-Kotlin-template A template that utilizes both Scala and Kotlin because why not (And also because I endorse programming hell) I don't care

null 1 Dec 25, 2021
Android utilities for easier and faster Kotlin programming.

Android utilities for easier and faster Kotlin programming. Download Gradle compile 'com.costular:kotlin-utils:0.1' How to use It depends on utilities

Diego Francisco Concepci├│n 51 Oct 3, 2022
Clean Code and Reactive Programming PlayGround for Bangkit 2021

Clean Code and Reactive Programming PlayGround for Bangkit 2021 Hello! This repo contains the IntelliJ project that I use to present my talk, "Clean A

raditya gumay 3 May 16, 2021
An example for who are all going to start learning Kotlin programming language to develop Android application.

Kotlin Example Here is an example for who are all going to start learning Kotlin programming language to develop Android application. First check this

Prabhakar Thota 56 Sep 16, 2022
Exercises for Functional Programming learning in Kotlin with Arrow

Exercises for Functional Programming in Kotlin with Arrow-kt Exercises and practice code for Functional Programming learning in Kotlin with Arrow Requ

Jack Lo 3 Nov 11, 2021
A pair of applications provide a direct means of integrating with one another via application programming interfaces (APIs)

What is a native integration? It's when a pair of applications provide a direct means of integrating with one another via application programming interfaces (APIs). Once integrated, data can flow between the apps and become more readily available to your employees.

Behruz Hurramov 2 Jan 17, 2022
Lambda-snake.kt - Snake Game Implementation for Web using Kotlin programming language compiled for Javascript

Projeto da disciplina de Linguagem de Programa├ž├úo Funcional 2021.1 (jan/2022) ??

Alex Candido 3 Jan 10, 2022
LifecycleMvp 1.2 0.0 Kotlin is MVP architecture implementation with Android Architecture Components and Kotlin language features

MinSDK 14+ Download Gradle Add to project level build.gradle allprojects { repositories { ... maven { url 'https://jitpack.io' }

Robert 20 Nov 9, 2021
Skeleton project for show the architecture of Android project using MVVM, Clean Architecture and Kotlin coroutine Flow

ClearScoreDemo Skeleton project for showing the architecture of Android project using MVVM, Clean architecture and Kotlin coroutine Flow App Architect

Plabon Modak 1 Mar 6, 2022
A fork of our clean architecture boilerplate, this time using the Android Architecture Components

Android Clean Architecture Components Boilerplate Note: This is a fork of our original Clean Architecture Boilerplate, except in this repo we have swi

Buffer 1.3k Jan 3, 2023