Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
skydoves committed Jul 23, 2019
1 parent cde87ef commit 35cf93e
Show file tree
Hide file tree
Showing 59 changed files with 1,922 additions and 14 deletions.
20 changes: 7 additions & 13 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ gen/
out/

# Gradle files
/.idea
.gradle/
build/

Expand All @@ -32,19 +33,19 @@ proguard/
# Android Studio captures folder
captures/

# IntelliJ
# Intellij
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
.idea/caches

# Mac
*.DS_Store

# Keystore files
# Uncomment the following line if you do not want to check your keystore files in.
#*.jks
*.jks

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
Expand All @@ -55,11 +56,4 @@ google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json

# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
freeline_project_description.json
192 changes: 191 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,192 @@
# Chamber
Simplifies sharing fields and communication between Android components with custom scopes that are lifecycle aware.

<p align="center">
<a href="https://opensource.org/licenses/Apache-2.0"><img alt="License" src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"/></a>
<a href="https://android-arsenal.com/api?level=15"><img alt="API" src="https://img.shields.io/badge/API-15%2B-brightgreen.svg?style=flat"/></a>
</p>

<p align="center">
Simplifies sharing fields and communication between <br>
Android components with custom scopes that are lifecycle aware.
</p>

> <p align="center">Android components are the essential building blocks of an Android application. <br>These independent components are loosely coupled. The benefit is that they are really independently reusable,<br> but it makes to hard communicate with each other. </p>
><p align="center"> The goal of this library is making easier to communicate and flow data with each other <br>component like Activity, Fragment, Services, etc.<br> And using custom scopes that are lifecycle aware makes<br> developers can designate scoped data holder on their taste.</p>
## When is useful?
>When we need to hold some immutable data and it needs to be synchronized as the same data at each other components. For example, there is `Activity A`, `Activity B`, `Activity C`. And we need to use the same data in all Activity A~C that can be changed. Then we should pass a parcelable data A to B and B to C and getting the changed data reversely through onActivityResult.
>Then how about the communication with fragments? We can solve it by implementing an interface, singleton pattern, observer pattern or etc, but the data flow would be quite complicated. Chamber helps to simplify those communications between Chamber scope owners.
## Download

### Gradle
Add a dependency code to your **module**'s `build.gradle` file.
```gradle
dependencies {
implementation "com.github.skydoves:chamber:1.0.0"
}
```

## Usage
Chamber is scoped data holder with custom scopes that are lifecycle aware.
### ChamberScope
The basic usage is creating a customized scope annotation using a `@ChamberScope` annotation. <br>
`@ChamberScope` is used to build custom scopes that are lifecycle aware. Each scope is a temporal data holder that has `ChamberField` data and lifecycle stack. It should be annotated a class (activity, fragment, repository or any classes) that has `ChamberField` fields.
```kotlin
@ChamberScope
@Retention(AnnotationRetention.RUNTIME)
annotation class UserScope
```

### ChamberField
ChamberField is an interactive class to the internal Chamber data holder and a lifecycleObserver <br>that can be observable.
It should be used with `@ShareProperty` annotation that has a key name. If we want to use the same synchronized value on the same custom scope and different classes, we should use the same key.

```kotlin
@ShareProperty("name") // name is a key name.
var username = ChamberField("skydoves") // ChamberField can be initialized with any object.
```

#### setValue
Using the `setValue` method, we can change the `ChamberField`'s value.
```kotlin
username.setValue("user name is changed")
```
#### postValue
Posts a task to a main thread to set the given value. So if you have a following code executed in the main thread:
```kotlin
username.postValue("a")
username.setValue("b")
```
The value `b` would be set at first and later the main thread would override it with the value `a`.

#### observe
We can observe the value is changed using the `observe` method.
```kotlin
username.observe {
log("data is changed to $it")
}
```

### ShareLifecycle
Chamber synchronizes the ChamberField that has the same scope and same key. <br>
Also pushes a lifecycleOwner to the Chamber's lifecycle stack.<br>
Here is an example that has _MainActivity_ and _SecondActivity_.

#### MainActivity
__Chamber__ will create a `@UserScope` data holder. <br>
when `Chamber.shareLifecycle` method called, the `name` field that has `nickname` key will be managed by Chamber and Chamber will observe the _MainActivity_'s lifecycle state.

```kotlin
@UserScope // custom scope
class MainActivity : AppCompatActivity() {

@ShareProperty("nickname")
private var name = ChamberField("skydoves")

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

Chamber.shareLifecycle(scopeOwner = this, lifecycleOwner = this)

name.value = "name value is changed"

startActivity(SecondActivity::class.java)
}
}
```

#### MainActivity -> SecondActivity
_MainActivity_ starts _SecondActivity_ using startActivity. <br>__Chamber__ will observe the _SecondActivity_'s lifecycle state. And the `name` field's value on the <br>_SecondActivity_ will be updated by __Chamber__ when `shareLifecycle` method called.
```kotlin
@UserScope
class SecondActivity : AppCompatActivity() {

@ShareProperty("nickname")
private var name = ChamberField("skydoves")

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)

Chamber.shareLifecycle(scopeOwner = this, lifecycleOwner = this)

// the value is "name value is changed". because it was set in MainActivity.
log("name value is .. ${username.value}")

name.value = "changed in SecondActivity"

finish()
}
}
```

#### SeondActivity -> MainActivity
`finish` method called in _SecondActivity_ and we come back to the _MainActivity_. <br>when _SecondActivity_'s lifecycle state is `onDestroy`, __Chamber__ will not interact anymore with the _SecondActivity_'s `ChamberField` and not observe lifecycle state. <br>And when _MainActivity_'s lifecycle state is `onResume`, __Chamber__ will update the `ChamberField`'s value in _MainActivity_.
```kotlin
@UserScope
class MainActivity : AppCompatActivity() {

@ShareProperty("nickname")
private var name = ChamberField("skydoves")

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)

// the value is "changed in SecondActivity". because it was set in SecondActivity.
name.observe {
log("name value is .. ${username.value}")
}
}
}
```
#### finish MainActivity
After all lifecycle owners are destroyed (all lifecycleOwners are popped from the __Chamber__'s lifecycle stack), the custom scope data space will be cleared in the internal data holder.

### Using on repository pattern
Architecturally, UI components should do work relate to UI works.<br>So it is more preferred to implement Chamber scope class on repository class.

```kotlin
@UserScope // custom scope
class MainActivityRepository(lifecycleOwner: LifecycleOwner) {

@ShareProperty("nickname")
var name = ChamberField("skydoves")

init {
// inject field data and add a lifecycleOwner to the UserScope scope stack.
Chamber.shareLifecycle(scopeOwner = this, lifecycleOwner = lifecycleOwner)
}
}

class MainActivity : AppCompatActivity() {

private val repository = MainActivityRepository(this)

// ...
}
```

## Find this library useful? :heart:
Support it by joining __[stargazers](https://github.com/skydoves/chamber/stargazers)__ for this repository. :star:

# License
```xml
Copyright 2019 skydoves (Jaewoong Eum)

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

http://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.
```
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
28 changes: 28 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply from: '../dependencies.gradle'

android {
compileSdkVersion versions.compileSdk
defaultConfig {
applicationId "com.skydoves.chamberdemo"
minSdkVersion versions.minSdk
targetSdkVersion versions.compileSdk
versionCode versions.versionCode
versionName versions.versionName
}
buildTypes {
release {
minifyEnabled false
}
}
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlin"
implementation "androidx.appcompat:appcompat:$versions.androidxAppcompat"
implementation project(":chamber")
}

apply from: '../spotless.gradle'
21 changes: 21 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
23 changes: 23 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.skydoves.chamberdemo">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity android:name=".ThirdActivity" />
<activity android:name=".SecondActivity" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
49 changes: 49 additions & 0 deletions app/src/main/java/com/skydoves/chamberdemo/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2019 skydoves
*
* 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
*
* http://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.
*/

package com.skydoves.chamberdemo

import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

private val repository = MainActivityRepository(this)

override fun onResume() {
super.onResume()
Log.e("Test", repository.username.value)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

repository.username.value = "skydoves on MainActivity"
repository.username.observe { Log.e("Test", "data is changed! : $it") }

button.setOnClickListener {
startActivity(Intent(this, SecondActivity::class.java))
}

button2.setOnClickListener {
Log.e("Test", repository.username.value)
}
}
}
Loading

0 comments on commit 35cf93e

Please sign in to comment.