Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
Artur Artikov committed Apr 21, 2021
2 parents daee768 + 0b0d12a commit a4ee998
Show file tree
Hide file tree
Showing 82 changed files with 2,978 additions and 400 deletions.
27 changes: 17 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,31 @@

Sesame is a set of architecture components for Android development. It is based on modern technologies including coroutines and Flow. Sesame is simple to learn and easy to use. It is ideally suited for MVVM and MVI architectures.

Some Sesame features are inspired by [RxPM](https://github.com/dmdevgo/RxPM) library.

## Components
[property](https://github.com/aartikov/Sesame/tree/master/sesame-property) - provides observable properties and one-time commands.
[dialog](https://github.com/aartikov/Sesame/tree/master/sesame-dialog) - allows to control dialogs from View Models.
[navigation](https://github.com/aartikov/Sesame/tree/master/sesame-navigation) - gives an universal way to navigate between screens.
[activable](https://github.com/aartikov/Sesame/tree/master/sesame-activable) - equips View Models with a very simple lifecycle.
[loading](https://github.com/aartikov/Sesame/tree/master/sesame-loading) - helps to manage state for data loading (including paged one).
[loop](https://github.com/aartikov/Sesame/tree/master/sesame-loop) - provides a simple MVI implementation.
[localized string](https://github.com/aartikov/Sesame/tree/master/sesame-localized-string) - helps to deal with string resources.
[form](https://github.com/aartikov/Sesame/tree/master/sesame-form) - provides input field validation.

Sesame components are independent. Use only that you like.
Sesame components are separate modules. Use only that you like.

## Gradle Setup
```gradle
dependencies {
implementation 'com.github.aartikov:sesame-property:1.0.0-alpha3'
implementation 'com.github.aartikov:sesame-dialog:1.0.0-alpha3'
implementation 'com.github.aartikov:sesame-navigation:1.0.0-alpha3'
implementation 'com.github.aartikov:sesame-activable:1.0.0-alpha3'
implementation 'com.github.aartikov:sesame-loading:1.0.0-alpha3'
implementation 'com.github.aartikov:sesame-loop:1.0.0-alpha3'
implementation 'com.github.aartikov:sesame-property:1.0.0-alpha4'
implementation 'com.github.aartikov:sesame-dialog:1.0.0-alpha4'
implementation 'com.github.aartikov:sesame-navigation:1.0.0-alpha4'
implementation 'com.github.aartikov:sesame-activable:1.0.0-alpha4'
implementation 'com.github.aartikov:sesame-loading:1.0.0-alpha4'
implementation 'com.github.aartikov:sesame-loop:1.0.0-alpha4'
implementation 'com.github.aartikov:sesame-localized-string:1.0.0-alpha4'
implementation 'com.github.aartikov:sesame-form:1.0.0-alpha4'
}
```

Expand All @@ -31,9 +37,10 @@ dependencies {

COUNTER - shows how to use properties and commands from [property](https://github.com/aartikov/Sesame/tree/master/sesame-property).
PROFILE - loads ordinary data with [loading](https://github.com/aartikov/Sesame/tree/master/sesame-loading).
DIALOGS - shows how to use [dialog](https://github.com/aartikov/Sesame/tree/master/sesame-dialog).
MOVIES - loads paged data with [loading](https://github.com/aartikov/Sesame/tree/master/lsesame-oading).
DIALOGS - shows how to use [dialog](https://github.com/aartikov/Sesame/tree/master/sesame-dialog) and [localized string](https://github.com/aartikov/Sesame/tree/master/sesame-localized-string).
MOVIES - loads paged data with [loading](https://github.com/aartikov/Sesame/tree/master/sesame-loading).
CLOCK - shows how to use [activable](https://github.com/aartikov/Sesame/tree/master/sesame-activable).
FORM - validates input fields with [form](https://github.com/aartikov/Sesame/tree/master/sesame-form).
The whole app - demonstrates [navigation](https://github.com/aartikov/Sesame/tree/master/sesame-navigation).

There is no sample for [loop](https://github.com/aartikov/Sesame/tree/master/sesame-loop). See [LoadingLoop](https://github.com/aartikov/Sesame/blob/master/sesame-loading/src/main/kotlin/me/aartikov/sesame/loading/simple/internal/LoadingLoop.kt) and [PagedLoadingLoop](https://github.com/aartikov/Sesame/blob/master/sesame-loading/src/main/kotlin/me/aartikov/sesame/loading/paged/internal/PagedLoadingLoop.kt) as good examples how to use it.
Expand All @@ -45,7 +52,7 @@ Artur Artikov <a href="mailto:[email protected]">[email protected]</a>
```
The MIT License (MIT)
Copyright (c) 2021 Artur Artikov
Copyright (c) 2021 Artur Artikov, Alexander Rovnov, Pavel Aleksandrov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ apply from: "androidLibraryConfig.gradle"
buildscript {
ext.kotlin_version = "1.4.31"
repositories {
mavenCentral()
google()
jcenter()
}
Expand All @@ -17,6 +18,7 @@ buildscript {

allprojects {
repositories {
mavenCentral()
google()
jcenter()
maven { url "https://kotlin.bintray.com/kotlinx/" }
Expand Down
24 changes: 18 additions & 6 deletions dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ ext {
lifecycle : "2.2.0",
desugaring : "1.1.1",
junit : "4.13.1",
androidxTest : "1.3.0",
androidxJunitExt : "1.1.2",
mockito : "2.28.2",
mockitoKotlin : "2.2.0",
fragment : "1.2.5",
coreKtx : "1.3.2",
appCompat : "1.2.0",
androidxAnnotation : "1.1.0",
constraintLayout : "2.0.4",
swiperefreshlayout : "1.1.0",
cardView : "1.0.0",
Expand All @@ -19,24 +22,30 @@ ext {
glide : "4.11.0",
hilt : "2.33-beta",
viewBindingDelegates: "1.4.1",
dateTime : "0.1.1"
dateTime : "0.1.1",
decoro : "1.5.0",
konfetti : "1.3.2"
]

desugaring = "com.android.tools:desugar_jdk_libs:${versions.desugaring}"

tests = [
junit : "junit:junit:${versions.junit}",
coroutinesTest: "org.jetbrains.kotlinx:kotlinx-coroutines-test:${versions.coroutines}",
mockitoCore : "org.mockito:mockito-core:${versions.mockito}",
mockitoInline : "org.mockito:mockito-inline:${versions.mockito}",
mockitoKotlin : "com.nhaarman.mockitokotlin2:mockito-kotlin:${versions.mockitoKotlin}",
junit : "junit:junit:${versions.junit}",
androidxTestCore: "androidx.test:core:${versions.androidxTest}",
androidxTestRunner: "androidx.test:runner:${versions.androidxTest}",
androidxJunitExt : "androidx.test.ext:junit:${versions.androidxJunitExt}",
coroutinesTest : "org.jetbrains.kotlinx:kotlinx-coroutines-test:${versions.coroutines}",
mockitoCore : "org.mockito:mockito-core:${versions.mockito}",
mockitoInline : "org.mockito:mockito-inline:${versions.mockito}",
mockitoKotlin : "com.nhaarman.mockitokotlin2:mockito-kotlin:${versions.mockitoKotlin}",
]

androidx = [
lifecycle : "androidx.lifecycle:lifecycle-runtime-ktx:${versions.lifecycle}",
fragment : "androidx.fragment:fragment-ktx:${versions.fragment}",
coreKtx : "androidx.core:core-ktx:${versions.coreKtx}",
appCompat : "androidx.appcompat:appcompat:${versions.appCompat}",
annotation : "androidx.annotation:annotation:${versions.androidxAnnotation}",
constraintLayout: "androidx.constraintlayout:constraintlayout:${versions.constraintLayout}",
swipeRefresh : "androidx.swiperefreshlayout:swiperefreshlayout:${versions.swiperefreshlayout}",
cardView : "androidx.cardview:cardview:${versions.cardView}"
Expand Down Expand Up @@ -65,4 +74,7 @@ ext {

viewBindingDelegates = "com.kirich1409.viewbindingpropertydelegate:vbpd-noreflection:${versions.viewBindingDelegates}"

decoro = "ru.tinkoff.decoro:decoro:${versions.decoro}"

konfetti = "nl.dionsegijn:konfetti:${versions.konfetti}"
}
2 changes: 1 addition & 1 deletion publish.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ apply plugin: 'org.jetbrains.dokka'

ext {
PUBLISH_GROUP_ID = 'com.github.aartikov'
PUBLISH_VERSION = '1.0.0-alpha3'
PUBLISH_VERSION = '1.0.0-alpha4'
DESCRIPTION = 'Sesame is a set of architecture components for Android development'

GITHUB_USER = 'aartikov'
Expand Down
3 changes: 2 additions & 1 deletion sample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ The sample application consists of several screens. Each screen demonstrates cer

COUNTER - shows how to use properties and commands from [property](https://github.com/aartikov/Sesame/tree/master/sesame-property).
PROFILE - loads ordinary data with [loading](https://github.com/aartikov/Sesame/tree/master/sesame-loading).
DIALOGS - shows how to use [dialog](https://github.com/aartikov/Sesame/tree/master/sesame-dialog).
DIALOGS - shows how to use [dialog](https://github.com/aartikov/Sesame/tree/master/sesame-dialog) and [localized string](https://github.com/aartikov/Sesame/tree/master/sesame-localized-string).
MOVIES - loads paged data with [loading](https://github.com/aartikov/Sesame/tree/master/sesame-loading).
CLOCK - shows how to use [activable](https://github.com/aartikov/Sesame/tree/master/sesame-activable).
FORM - validates input fields with [form](https://github.com/aartikov/Sesame/tree/master/sesame-form).
The whole app - demonstrates [navigation](https://github.com/aartikov/Sesame/tree/master/sesame-navigation).

There is no sample for [loop](https://github.com/aartikov/Sesame/tree/master/sesame-loop). See [LoadingLoop](https://github.com/aartikov/Sesame/blob/master/sesame-loading/src/main/kotlin/me/aartikov/sesame/loading/simple/internal/LoadingLoop.kt) and [PagedLoadingLoop](https://github.com/aartikov/Sesame/blob/master/sesame-loading/src/main/kotlin/me/aartikov/sesame/loading/paged/internal/PagedLoadingLoop.kt) as good examples how to use it.
4 changes: 4 additions & 0 deletions sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ dependencies {

implementation project(":sesame-activable")
implementation project(":sesame-dialog")
implementation project(":sesame-form")
implementation project(":sesame-loading")
implementation project(":sesame-localized-string")
implementation project(":sesame-navigation")
implementation project(":sesame-property")

Expand All @@ -62,4 +64,6 @@ dependencies {

implementation glide
implementation viewBindingDelegates
implementation decoro
implementation konfetti
}
20 changes: 18 additions & 2 deletions sample/src/main/kotlin/me/aartikov/sesamesample/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import me.aartikov.sesamesample.base.BaseFragment
import me.aartikov.sesamesample.clock.ClockFragment
import me.aartikov.sesamesample.counter.CounterFragment
import me.aartikov.sesamesample.dialogs.DialogsFragment
import me.aartikov.sesamesample.form.FormFragment
import me.aartikov.sesamesample.menu.MenuFragment
import me.aartikov.sesamesample.movies.ui.MoviesFragment
import me.aartikov.sesamesample.profile.ui.ProfileFragment
Expand All @@ -27,14 +28,22 @@ class MainActivity : AppCompatActivity(), NavigationMessageHandler {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

navigationMessageDispatcher.attach(this)
navigator = FragmentNavigator(R.id.container, supportFragmentManager)

if (savedInstanceState == null) {
navigator.setRoot(MenuFragment())
}
}

override fun onResumeFragments() {
super.onResumeFragments()
navigationMessageDispatcher.resume()
}

override fun onPause() {
navigationMessageDispatcher.pause()
super.onPause()
}

override fun handleNavigationMessage(message: NavigationMessage): Boolean {
when (message) {
is Back -> {
Expand All @@ -46,11 +55,18 @@ class MainActivity : AppCompatActivity(), NavigationMessageHandler {
is OpenDialogsScreen -> navigator.goTo(DialogsFragment())
is OpenMoviesScreen -> navigator.goTo(MoviesFragment())
is OpenClockScreen -> navigator.goTo(ClockFragment())
is OpenFormScreen -> navigator.goTo(FormFragment())
}
updateTitle()
return true
}

override fun onBackPressed() {
(navigator.currentScreen as? BaseFragment<*>)?.onBackPressed()
}

private fun updateTitle() {
val titleRes = (navigator.currentScreen as? BaseFragment<*>)?.titleRes ?: R.string.app_name
supportActionBar?.setTitle(titleRes)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ object OpenCounterScreen : NavigationMessage
object OpenProfileScreen : NavigationMessage
object OpenDialogsScreen : NavigationMessage
object OpenMoviesScreen : NavigationMessage
object OpenClockScreen : NavigationMessage
object OpenClockScreen : NavigationMessage
object OpenFormScreen : NavigationMessage
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.fragment.app.createViewModelLazy
import androidx.lifecycle.LifecycleOwner
import me.aartikov.sesame.activable.bindToLifecycle
import me.aartikov.sesame.dialog.DialogObserver
import me.aartikov.sesame.form.view.ControlObserver
import me.aartikov.sesame.navigation.NavigationMessageDispatcher
import me.aartikov.sesame.navigation.bind
import me.aartikov.sesame.property.PropertyObserver
Expand All @@ -18,12 +19,14 @@ import kotlin.reflect.KClass
abstract class BaseFragment<VM : BaseViewModel>(
@LayoutRes contentLayoutId: Int,
vmClass: KClass<VM>
) : Fragment(contentLayoutId), PropertyObserver, DialogObserver {
) : Fragment(contentLayoutId), PropertyObserver, DialogObserver, ControlObserver {

override val propertyObserverLifecycleOwner: LifecycleOwner get() = viewLifecycleOwner
override val dialogObserverLifecycleOwner: LifecycleOwner get() = viewLifecycleOwner
val vm: VM by createViewModelLazy(vmClass, { viewModelStore })

abstract val titleRes: Int

@Inject
internal lateinit var navigationMessageDispatcher: NavigationMessageDispatcher

Expand All @@ -35,7 +38,7 @@ abstract class BaseFragment<VM : BaseViewModel>(
vm.navigationMessageQueue.bind(navigationMessageDispatcher, node = this, viewLifecycleOwner)

vm.showError bind {
Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), it.resolve(requireContext()), Toast.LENGTH_SHORT).show()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.Flow
import me.aartikov.sesame.activable.Activable
import me.aartikov.sesame.activable.activableFlow
import me.aartikov.sesame.localizedstring.LocalizedString
import me.aartikov.sesame.navigation.NavigationMessage
import me.aartikov.sesame.navigation.NavigationMessageQueue
import me.aartikov.sesame.property.PropertyHost
Expand All @@ -16,14 +17,14 @@ abstract class BaseViewModel : ViewModel(), PropertyHost, Activable by Activable
override val propertyHostScope get() = viewModelScope

val navigationMessageQueue = NavigationMessageQueue()
val showError = command<String>()
val showError = command<LocalizedString>()

protected fun navigate(message: NavigationMessage) {
navigationMessageQueue.send(message)
}

protected fun showError(e: Throwable) {
showError(e.message ?: "Error")
showError(LocalizedString.raw(e.message ?: "Error"))
}

open fun onBackPressed() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import me.aartikov.sesamesample.databinding.FragmentClockBinding
@AndroidEntryPoint
class ClockFragment : BaseFragment<ClockViewModel>(R.layout.fragment_clock, ClockViewModel::class) {

override val titleRes: Int = R.string.clock_title

private val binding by viewBinding(FragmentClockBinding::bind)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import me.aartikov.sesamesample.databinding.FragmentCounterBinding
@AndroidEntryPoint
class CounterFragment : BaseFragment<CounterViewModel>(R.layout.fragment_counter, CounterViewModel::class) {

override val titleRes: Int = R.string.counter_title

private val binding by viewBinding(FragmentCounterBinding::bind)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Expand All @@ -25,7 +27,9 @@ class CounterFragment : BaseFragment<CounterViewModel>(R.layout.fragment_counter
vm::minusButtonEnabled bind minusButton::setEnabled
vm::plusButtonEnabled bind plusButton::setEnabled

vm.showMessage bind { Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show() }
vm.showMessage bind {
Toast.makeText(requireContext(), it.resolve(requireContext()), Toast.LENGTH_SHORT).show()
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package me.aartikov.sesamesample.counter

import dagger.hilt.android.lifecycle.HiltViewModel
import me.aartikov.sesamesample.base.BaseViewModel
import me.aartikov.sesame.localizedstring.LocalizedString
import me.aartikov.sesame.property.command
import me.aartikov.sesame.property.computed
import me.aartikov.sesame.property.state
import me.aartikov.sesamesample.R
import me.aartikov.sesamesample.base.BaseViewModel
import javax.inject.Inject

@HiltViewModel
Expand All @@ -20,7 +22,7 @@ class CounterViewModel @Inject constructor() : BaseViewModel() {
val minusButtonEnabled by computed(::count) { it > 0 }
val plusButtonEnabled by computed(::count) { it < MAX_COUNT }

val showMessage = command<String>()
val showMessage = command<LocalizedString>()

fun onMinusButtonClicked() {
if (minusButtonEnabled) {
Expand All @@ -34,7 +36,7 @@ class CounterViewModel @Inject constructor() : BaseViewModel() {
}

if (count == MAX_COUNT) {
showMessage("It's enough!")
showMessage(LocalizedString.resource(R.string.overflow_message))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package me.aartikov.sesamesample.dialogs

enum class DialogResult {
OK, CANCEL
Ok, Cancel
}
Loading

0 comments on commit a4ee998

Please sign in to comment.