Skip to content

Commit

Permalink
Merge pull request Hukumister#63 from HaronCode/task/store-keeper
Browse files Browse the repository at this point in the history
task/store keeper
  • Loading branch information
kdk96 authored Jan 14, 2021
2 parents 455ca35 + 1d108c1 commit 3393eee
Show file tree
Hide file tree
Showing 26 changed files with 210 additions and 44 deletions.
1 change: 1 addition & 0 deletions buildSrc/buildSrc/src/main/kotlin/Deps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ object Deps {
object androidx {
const val appCompat = "androidx.appcompat:appcompat:${Versions.androidx.appCompat}"
const val lifecycleKtx = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.androidx.lifecycle}"
const val viewModel = "androidx.lifecycle:lifecycle-viewmodel:${Versions.androidx.lifecycle}"
const val coreKtx = "androidx.core:core-ktx:${Versions.androidx.core}"
const val material = "com.google.android.material:material:${Versions.androidx.material}"
const val constraintLayout = "androidx.constraintlayout:constraintlayout:${Versions.androidx.constraintLayout}"
Expand Down
4 changes: 2 additions & 2 deletions buildSrc/buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
object Versions {
const val kotlin = "1.4.20"
const val kotlin = "1.4.21"

object kotlinx {
const val coroutines = "1.4.2"
Expand All @@ -10,7 +10,7 @@ object Versions {
const val minSdk = 21
const val compileSdk = 30
const val targetSdk = 30
const val buildTools = "30.0.2"
const val buildTools = "30.0.3"
}

object androidx {
Expand Down
6 changes: 6 additions & 0 deletions buildSrc/src/main/kotlin/publish/PublishPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class PublishPlugin : Plugin<Project> {
user = authData.user
key = authData.key
publish = publishingData.publishAfterUpload
if (target.plugins.hasPlugin("org.jetbrains.kotlin.android")) {
setPublications("gemini-store-keeper")
}
pkg.apply {
repo = publishingData.repoName
name = target.name
Expand All @@ -57,6 +60,9 @@ class PublishPlugin : Plugin<Project> {
}

private fun setupBintrayPublishing(target: Project) {
// TODO: make plugin universal
if (target.plugins.hasPlugin("org.jetbrains.kotlin.android")) return

target.tasks.named(BintrayUploadTask.getTASK_NAME(), BintrayUploadTask::class.java) {
doFirst {
val publishing = project.extensions.getByType(PublishingExtension::class)
Expand Down
2 changes: 2 additions & 0 deletions gemini-binder/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ kotlin {
dependencies {
implementation(Deps.androidx.appCompat)
implementation(Deps.androidx.lifecycleKtx)
implementation(Deps.androidx.viewModel)
api(project(":gemini-store-keeper"))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.haroncode.gemini.binder

import androidx.lifecycle.LifecycleOwner
import com.haroncode.gemini.binder.coordinator.Coordinator
import com.haroncode.gemini.binder.rule.BindingRulesFactory
import com.haroncode.gemini.lifecycle.StoreLifecycleObserver
import com.haroncode.gemini.lifecycle.strategy.LifecycleStrategy
import kotlinx.coroutines.Dispatchers

internal class BinderImpl<View : LifecycleOwner>(
private val factory: BindingRulesFactory<View>,
private val lifecycleStrategy: LifecycleStrategy
) : Binder<View> {

override fun bind(view: View) {
val bindingRules = factory.create(view)
val coordinator = Coordinator(Dispatchers.Main.immediate, bindingRules)
val storeLifecycleObserver = StoreLifecycleObserver(lifecycleStrategy, coordinator)
view.lifecycle.addObserver(storeLifecycleObserver)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package com.haroncode.gemini.binder

import com.haroncode.gemini.binder.rule.BindingRulesFactory

@Deprecated(
message = "Will be removed in upcoming release, have to use getStore extension to retain and cancel store instance in ViewModel",
replaceWith = ReplaceWith("getStore(provider)", imports = ["com.haroncode.gemini.keeper.getStore"])
)
internal object BindingRulesFactoryManager {

private val factories: MutableMap<String, BindingRulesFactory<*>> = mutableMapOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import com.haroncode.gemini.lifecycle.strategy.LifecycleStrategy
import kotlinx.coroutines.Dispatchers
import java.util.UUID

@Deprecated(
message = "Will be removed in upcoming release, have to use getStore extension to retain and cancel store instance in ViewModel",
replaceWith = ReplaceWith("getStore(provider)", imports = ["com.haroncode.gemini.keeper.getStore"])
)
internal class RestoreBinder<View : SavedStateRegistryOwner>(
private val factoryProvider: () -> BindingRulesFactory<View>,
private val lifecycleStrategy: LifecycleStrategy,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import com.haroncode.gemini.lifecycle.StoreLifecycleObserver
import com.haroncode.gemini.lifecycle.strategy.LifecycleStrategy
import kotlinx.coroutines.Dispatchers

@Deprecated(
message = "Will be removed in upcoming release, have to use getStore extension to retain and cancel store instance in ViewModel",
replaceWith = ReplaceWith("getStore(provider)", imports = ["com.haroncode.gemini.keeper.getStore"])
)
internal class SimpleBinder<View : SavedStateRegistryOwner>(
private val factoryProvider: () -> BindingRulesFactory<View>,
private val lifecycleStrategy: LifecycleStrategy,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package com.haroncode.gemini.binder

import androidx.annotation.CheckResult
import androidx.lifecycle.LifecycleOwner
import androidx.savedstate.SavedStateRegistryOwner
import com.haroncode.gemini.binder.rule.BindingRulesFactory
import com.haroncode.gemini.lifecycle.strategy.LifecycleStrategy
import com.haroncode.gemini.lifecycle.strategy.StartStopStrategy

actual object StoreViewBinding {

@Deprecated(
message = "Will be removed in upcoming release, have to use getStore extension to retain and cancel store instance in ViewModel",
replaceWith = ReplaceWith("StoreViewBinding.with(factory, lifecycleStrategy)")
)
@CheckResult
fun <T : SavedStateRegistryOwner> with(
lifecycleStrategy: LifecycleStrategy = StartStopStrategy,
Expand All @@ -17,12 +22,25 @@ actual object StoreViewBinding {
lifecycleStrategy = lifecycleStrategy,
)

@Deprecated(
message = "Will be removed in upcoming release, have to use getStore extension to retain and cancel store instance in ViewModel",
replaceWith = ReplaceWith("StoreViewBinding.with(factory, lifecycleStrategy)")
)
@CheckResult
fun <T : SavedStateRegistryOwner> withRestore(
lifecycleStrategy: LifecycleStrategy = StartStopStrategy,
factoryProvider: () -> BindingRulesFactory<T>,
): Binder<T> = RestoreBinder(
factoryProvider = factoryProvider,
lifecycleStrategy = lifecycleStrategy,
)

@CheckResult
fun <T : LifecycleOwner> with(
factory: BindingRulesFactory<T>,
lifecycleStrategy: LifecycleStrategy = StartStopStrategy
): Binder<T> = BinderImpl(
factory = factory,
lifecycleStrategy = lifecycleStrategy
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import androidx.savedstate.SavedStateRegistryOwner
/**
* @author kdk96
*/
@Deprecated(
message = "Will be removed in upcoming release, have to use getStore extension to retain and cancel store instance in ViewModel",
replaceWith = ReplaceWith("getStore(provider)", imports = ["com.haroncode.gemini.keeper.getStore"])
)
internal abstract class ExtendedLifecycleObserver : LifecycleObserver, SavedStateRegistry.SavedStateProvider {

private var instanceStateSaved = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import com.haroncode.gemini.binder.rule.AutoCancelStoreRule
* @author HaronCode
* @author kdk96
*/
@Deprecated(
message = "Will be removed in upcoming release, have to use getStore extension to retain and cancel store instance in ViewModel",
replaceWith = ReplaceWith("getStore(provider)", imports = ["com.haroncode.gemini.keeper.getStore"])
)
internal class StoreCancelObserver(
private val autoCancelStoreRuleCollection: Collection<AutoCancelStoreRule>
) : ExtendedLifecycleObserver() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package com.haroncode.gemini.binder.rule
import com.haroncode.gemini.element.Store
import kotlinx.coroutines.cancel

@Deprecated(
message = "Will be removed in upcoming release, have to use getStore extension to retain and cancel store instance in ViewModel",
replaceWith = ReplaceWith("getStore(provider)", imports = ["com.haroncode.gemini.keeper.getStore"])
)
class AutoCancelStoreRule(
private val store: Store<*, *, *>
) : BindingRule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class BindingRuleListBuilder {
rules += bindingRuleProvider.invoke()
}

@Deprecated(
message = "Will be removed in upcoming release, have to use getStore extension to retain and cancel store instance in ViewModel",
replaceWith = ReplaceWith("getStore(provider)", imports = ["com.haroncode.gemini.keeper.getStore"])
)
fun autoCancel(storeProvider: () -> Store<*, *, *>) {
rules += AutoCancelStoreRule(storeProvider.invoke())
}
Expand Down
52 changes: 52 additions & 0 deletions gemini-store-keeper/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
plugins {
id("com.android.library")
kotlin("android")
id("ktlint-plugin")
id("publish-plugin")
}

android {
compileSdkVersion(Versions.android.compileSdk)
buildToolsVersion(Versions.android.buildTools)

defaultConfig {
minSdkVersion(Versions.android.minSdk)
}

libraryVariants.all {
generateBuildConfigProvider.configure { enabled = false }
}
}

dependencies {
implementation(Deps.kotlinx.coroutines)
implementation(Deps.androidx.viewModel)
api(project(":gemini-core"))
}

val geminiGroup = findProperty("group") as String
val geminiVersion = findProperty("version") as String

version = geminiVersion
group = geminiGroup

val sourcesJar by tasks.creating(Jar::class) {
archiveClassifier.set("sources")
from(android.sourceSets.getByName("main").java.srcDirs)
}

afterEvaluate {
publishing {
publications {
create<MavenPublication>("gemini-store-keeper") {
groupId = geminiGroup
artifactId = "gemini-store-keeper"
version = geminiVersion

from(components["release"])

artifact(sourcesJar)
}
}
}
}
2 changes: 2 additions & 0 deletions gemini-store-keeper/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.haroncode.gemini.keeper" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.haroncode.gemini.keeper

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import com.haroncode.gemini.element.Store
import kotlinx.coroutines.cancel

/**
* @author kdk96
*/

inline fun <reified T, Action : Any, State : Any, Event : Any> ViewModelStoreOwner.getStore(
noinline provider: () -> T
) where T : Store<Action, State, Event> =
getStore(T::class.java.canonicalName!!, provider)

fun <T, Action : Any, State : Any, Event : Any> ViewModelStoreOwner.getStore(
key: String,
provider: () -> T
) where T : Store<Action, State, Event> =
ViewModelProvider(this)
.get(StoreKeeperViewModel::class.java)
.get(key, provider)

internal class StoreKeeperViewModel : ViewModel() {

private val stores = HashMap<String, Store<*, *, *>>()

@Suppress("UNCHECKED_CAST")
fun <T, Action : Any, State : Any, Event : Any> get(
key: String,
provider: () -> T
) where T : Store<Action, State, Event> =
stores.getOrPut(key, provider) as T

override fun onCleared() {
stores.values.forEach { store -> store.coroutineScope.cancel() }
stores.clear()
}
}
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ package com.haroncode.gemini.sample.presentation.justreducer
import com.haroncode.gemini.binder.rule.BindingRulesFactory
import com.haroncode.gemini.binder.rule.DelegateBindingRulesFactory
import com.haroncode.gemini.binder.rule.bindingRulesFactory
import com.haroncode.gemini.sample.di.scope.PerFragment
import com.haroncode.gemini.sample.ui.CounterStoreViewDelegate
import com.haroncode.gemini.sample.util.getStore
import javax.inject.Inject
import javax.inject.Provider

@PerFragment
class CounterBindingFactory @Inject constructor(
private val store: CounterStore
private val storeProvider: Provider<CounterStore>
) : DelegateBindingRulesFactory<CounterStoreViewDelegate>() {

override val bindingRulesFactory: BindingRulesFactory<CounterStoreViewDelegate> = bindingRulesFactory { view ->
baseRule { store to view }
autoCancel { store } // magic is here )))
val counterStore = view.getStore(storeProvider)
baseRule { counterStore to view }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ import com.haroncode.gemini.binder.rule.BindingRulesFactory
import com.haroncode.gemini.binder.rule.DelegateBindingRulesFactory
import com.haroncode.gemini.binder.rule.bindEventTo
import com.haroncode.gemini.binder.rule.bindingRulesFactory
import com.haroncode.gemini.sample.di.scope.PerFragment
import com.haroncode.gemini.sample.ui.AuthFragment
import com.haroncode.gemini.sample.util.getStore
import javax.inject.Inject
import javax.inject.Provider

@PerFragment
class AuthBindingFactory @Inject constructor(
private val store: AuthStore
private val storeProvider: Provider<AuthStore>
) : DelegateBindingRulesFactory<AuthFragment>() {

override val bindingRulesFactory: BindingRulesFactory<AuthFragment> = bindingRulesFactory { view ->
val store = view.getStore(storeProvider)
baseRule { store to view }
rule { store bindEventTo view }
autoCancel { store } // magic is here )))
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,21 @@ import com.haroncode.gemini.sample.presentation.onlyaction.AuthStore.Action
import com.haroncode.gemini.sample.presentation.onlyaction.AuthStore.Action.LoginClick
import com.haroncode.gemini.sample.presentation.onlyaction.AuthStore.State
import javax.inject.Inject
import javax.inject.Provider

class AuthFragment :
PublisherFragment<Action, State>(R.layout.fragment_auth),
StoreEventListener<AuthStore.Event> {

@Inject
lateinit var factory: Provider<AuthBindingFactory>
lateinit var factory: AuthBindingFactory

private var _binding: FragmentAuthBinding? = null
private val binding get() = _binding!!

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

StoreViewBinding.withRestore(factoryProvider = factory::get)
StoreViewBinding.with(factory)
.bind(this)
}

Expand Down
Loading

0 comments on commit 3393eee

Please sign in to comment.