Skip to content

Native Module refreshment #408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ jobs:
name: Flow check
command: yarn test:flow

"Test: units":
<<: *js_defaults
steps:
- *addWorkspace
- run:
name: Unit tests
command: yarn test:unit

"Test: iOS e2e":
<<: *macos_defaults
steps:
Expand Down Expand Up @@ -306,14 +314,19 @@ workflows:
- "Test: flow":
requires:
- "Setup environment"
- "Test: units":
requires:
- "Setup environment"
- "Test: iOS e2e":
requires:
- "Test: lint"
- "Test: flow"
- "Test: units"
- "Build: Android release apk":
requires:
- "Test: lint"
- "Test: flow"
- "Test: units"
# - "Test: Android e2e":
# requires:
# - "Test: lint"
Expand Down
54 changes: 41 additions & 13 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,43 @@ def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}

def useDedicatedExecutor = rootProject.hasProperty('AsyncStorage_dedicatedExecutor')
? rootProject.properties['AsyncStorage_dedicatedExecutor']
: false


buildscript {
// The Android Gradle plugin is only required when opening the android folder stand-alone.
// This avoids unnecessary downloads and potential conflicts when the library is included as a
// module dependency in an application project.
if (project == rootProject) {
repositories {
google()
def isRootProject = project == rootProject

ext.coroutinesVersion = "1.3.8"
ext.roomVersion = "2.2.5"

// kotlin version can be set by setting ext.kotlinVersion or having it in gradle.properties in root
ext.asyncStorageKtVersion = rootProject.ext.has('kotlinVersion')
? rootProject.ext.get('kotlinVersion')
: properties['kotlinVersion']
? properties['kotlinVersion']
: "1.3.72"

repositories {
if (isRootProject) {
jcenter()
}
dependencies {
google()
mavenCentral()
}

dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$asyncStorageKtVersion"
if (isRootProject) {
// The Android Gradle plugin is only required when opening the android folder stand-alone.
// This avoids unnecessary downloads and potential conflicts when the library is included as a
// module dependency in an application project.
classpath 'com.android.tools.build:gradle:3.5.0'
}
}


}

// AsyncStorage has default size of 6MB.
Expand All @@ -24,16 +48,13 @@ buildscript {
long dbSizeInMB = 6L

def newDbSize = rootProject.properties['AsyncStorage_db_size_in_MB']

if( newDbSize != null && newDbSize.isLong()) {
if (newDbSize != null && newDbSize.isLong()) {
dbSizeInMB = newDbSize.toLong()
}

def useDedicatedExecutor = rootProject.hasProperty('AsyncStorage_dedicatedExecutor')
? rootProject.properties['AsyncStorage_dedicatedExecutor']
: false

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'

android {
compileSdkVersion safeExtGet('compileSdkVersion', 28)
Expand Down Expand Up @@ -62,4 +83,11 @@ repositories {
dependencies {
//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+' // From node_modules
//noinspection GradleDependency
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$asyncStorageKtVersion"

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
implementation "androidx.room:room-runtime:$roomVersion"
implementation "androidx.room:room-ktx:$roomVersion"
kapt "androidx.room:room-compiler:$roomVersion"
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.reactnativecommunity.asyncstorage.next.AsyncStorageModuleNext;

public class AsyncStoragePackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(new AsyncStorageModule(reactContext));
return Arrays.<NativeModule>asList(
new AsyncStorageModule(reactContext),
new AsyncStorageModuleNext(reactContext)
);
}

// Deprecated in RN 0.47
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* Copyright (c) Krzysztof Borowy
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.reactnativecommunity.asyncstorage.next

import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext

class AsyncStorageModuleNext(reactAppContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactAppContext), CoroutineScope {
companion object {
const val MODULE_NAME = "RNC_AsyncStorageNext"
}

override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + CoroutineName("AsyncStorageCoroutine")

private val db: ASDao
get() = AsyncStorageDB.getDatabase(reactApplicationContext).getASDao()

override fun getName() = MODULE_NAME

@ReactMethod
fun getSingle(key: String, promise: Promise) {
launch {
val value = db.get(key)
promise.resolve(value)
}
}

@ReactMethod
fun setSingle(key: String, value: String?, promise: Promise) {
val entry = AsyncStorageEntry(key, value)
launch {
db.set(entry)
promise.resolve(true)
}
}

@ReactMethod
fun deleteSingle(key: String, promise: Promise) {
val entry = AsyncStorageEntry(key)
launch {
db.delete(entry)
promise.resolve(true)
}
}

@ReactMethod
fun getMany(keys: ReadableArray, promise: Promise) {
val queryKeys = keys.toKeyList()

launch {
val entries = db.getMany(queryKeys).toReadableMap()
promise.resolve(entries)
}
}

@ReactMethod
fun setMany(entries: ReadableMap, promise: Promise) {
val entryList = entries.toAsyncStorageEntries()

launch {
db.setMany(entryList)
promise.resolve(true)
}
}

@ReactMethod
fun deleteMany(keys: ReadableArray, promise: Promise) {
val keysToDelete = keys.toKeyList()

launch {
db.deleteMany(keysToDelete)
promise.resolve(true)
}
}

@ReactMethod
fun getAllKeys(promise: Promise) {
launch {
val keys = db.keys().toReadableArray()
promise.resolve(keys)
}
}

@ReactMethod
fun dropDatabase(promise: Promise) {
launch {
db.dropDatabase()
promise.resolve(true)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* Copyright (c) Krzysztof Borowy
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.reactnativecommunity.asyncstorage.next

import android.content.Context
import androidx.room.ColumnInfo
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Delete
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.Room
import androidx.room.RoomDatabase

typealias KeyType = String
typealias ValueType = String?

// todo: change those to use old DB
// todo: unit tests
const val DB_NAME = "AsyncStorageNextDB"
const val DB_VERSION = 1
const val TABLE_NAME = "as_table"
const val KEY_COLUMN = "as_keys"
const val VALUE_COLUMN = "as_value"

// The only table in this DB

@Entity(tableName = TABLE_NAME)
data class AsyncStorageEntry(
@PrimaryKey
@ColumnInfo(name = KEY_COLUMN)
val key: String,
@ColumnInfo(name = VALUE_COLUMN)
val value: String? = null
)

@Dao
interface ASDao {

@Query("SELECT $VALUE_COLUMN FROM $TABLE_NAME WHERE $KEY_COLUMN = :key")
suspend fun get(key: KeyType): ValueType

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun set(entry: AsyncStorageEntry)

@Delete
suspend fun delete(entry: AsyncStorageEntry)

@Query("SELECT $KEY_COLUMN FROM $TABLE_NAME")
suspend fun keys(): List<KeyType>

@Query("DELETE from $TABLE_NAME")
suspend fun dropDatabase()

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun setMany(entries: List<AsyncStorageEntry>)

@Query("SELECT * FROM $TABLE_NAME WHERE $KEY_COLUMN in (:keys)")
suspend fun getMany(keys: List<KeyType>): List<AsyncStorageEntry>

@Query("DELETE FROM $TABLE_NAME WHERE $KEY_COLUMN in (:keys)")
suspend fun deleteMany(keys: List<KeyType>)
}

@Database(entities = [AsyncStorageEntry::class], version = DB_VERSION)
abstract class AsyncStorageDB : RoomDatabase() {

companion object {
private var instance: AsyncStorageDB? = null

fun getDatabase(context: Context): AsyncStorageDB {

var inst = instance

if (inst != null) {
return inst
}
synchronized(this) {
inst = Room
.databaseBuilder(context, AsyncStorageDB::class.java, DB_NAME)
.build()

instance = inst
return instance!!
}
}
}

abstract fun getASDao(): ASDao
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright (c) Krzysztof Borowy
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.reactnativecommunity.asyncstorage.next

import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap

fun ReadableArray.toKeyList(): List<String> {
val keys: MutableList<String> = mutableListOf()
for (index in 0 until this.size()) {
val key = getString(index)
if (key != null) {
keys.add(key)
}
}
return keys
}

fun ReadableMap.toAsyncStorageEntries(): List<AsyncStorageEntry> {
val entryList = mutableListOf<AsyncStorageEntry>()
val keyIterator = keySetIterator()
while (keyIterator.hasNextKey()) {
val key = keyIterator.nextKey()
val value = getString(key)
entryList.add(AsyncStorageEntry(key, value))
}
return entryList
}

fun List<KeyType>.toReadableArray(): ReadableArray {
val keyArray = Arguments.createArray()
forEach { key ->
keyArray.pushString(key)
}
return keyArray
}

fun List<AsyncStorageEntry>.toReadableMap(): ReadableMap {
val result = Arguments.createMap()
forEach {
result.putString(it.key, it.value)
}
return result
}
Loading