Skip to content

Commit e01da65

Browse files
authored
Merge pull request #1 from patjackson52/feature/database
Feature/database
2 parents 53f4966 + 1907a90 commit e01da65

File tree

143 files changed

+1018
-4754
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

143 files changed

+1018
-4754
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
/.idea/modules.xml
66
/.idea/workspace.xml
77
/.idea
8-
/iOS/NameGame/.idea/
98
.DS_Store
109
/build
1110
/common/build

README.md

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
1-
# Open Library
1+
# Open Library Sample Project
22

3-
__Android Build & Tests__
43

54

6-
[![CircleCI](https://circleci.com/gh/patjackson52/NameGameKotlinMpp.svg?style=svg)](https://circleci.com/gh/patjackson52/NameGameKotlinMpp)
7-
8-
__iOS Build & Tests____
9-
10-
[![Bitrise](https://app.bitrise.io/app/3eaf4fa6c0750504/status.svg?token=N7jdGFn6dvfLcuKZaBMW1g)](https://app.bitrise.io/app/3eaf4fa6c0750504#/builds)
11-
12-
A Kotlin multiplatform (Android/iOS) Library. The app utilizes the following:
5+
A Kotlin multiplatform (Android/iOS) Library app. The app utilizes the following:
136
* [Ktor](https://ktor.io/clients/http-client.html) for networking
147
* [Kotlinx Serialization](https://github.com/Kotlin/kotlinx.serialization)
158
* [Multiplatform Settings](https://github.com/russhwolf/multiplatform-settings)
9+
* [SqlDelight](https://github.com/square/sqldelight)
10+
* [ReduxKotlin](https://reduxkotlin.org)
11+
1612

1713

1814
## Android
@@ -24,21 +20,21 @@ or opened and ran in Android Studio
2420

2521

2622
## iOS
27-
The iOS workspace in `/iOS/NameGame` can be open and ran from xCode or AppCode. A run script has been added to the build phase that will compile the common code into a framework which can be used for the project.
23+
iOS can be implemented quickly now that code is in a common module.
2824

2925

3026
## Architecture
3127

32-
A `GameEngine` object holds the state of the app in a redux store and provides a methods for views (fragments/UIViewControllers) to "attach". The `GameEngine` is initialized in the Application class on Android, and the AppDelegate on iOS. Because this `GameEngine` and the store is created at the application scope, the application state survives between ViewControllers/Fragments and rotation. Each view must attach/detach from the GameEngine when it is visible. `GameEngine.attachView(view)` returns the appropriate presenter for the view.
28+
A `LibraryApp` object holds the state of the app in a redux store and provides a methods for views (fragments/UIViewControllers) to "attach". The `LibraryApp` is initialized in the Application class on Android, and the AppDelegate on iOS. Because this `LibraryApp` and the store is created at the application scope, the application state survives between ViewControllers/Fragments and rotation. Each view must attach/detach from the GameEngine when it is visible. `LibraryApp.attachView(view)` returns the appropriate presenter for the view.
3329

34-
`BaseNameGameViewFragment` & `BaseNameGameViewController` handle attaching/detaching the presenter at the appropriate lifecycle methods. Each Fragment/ViewController extends from these.
30+
`BaseLibraryViewFragment` & `BaseLibraryViewController` handle attaching/detaching the presenter at the appropriate lifecycle methods. Each Fragment/ViewController extends from these.
3531

36-
An MVP arch is used with a redux store as the 'Model'. This approach allows maximum reuse of code and a simple contract for the platforms to satisfy. Presenters send `ViewStates` (simple data classes with fields needed to render UI) to the View interface. The View implementation has a reference to the presenter, and calls methods on the presetner for user interaction. This creates a unidirectional dataflow:
32+
An MVP arch is used with a redux store as the 'Model'. This approach allows maximum reuse of code and a simple contract for the platforms to satisfy. Presenters send `ViewStates` (simple data classes with fields needed to render UI) to the View interface. The View implementation has a reference to the presenter, and calls methods on the presenter for user interaction. This creates a unidirectional dataflow:
3733

3834
User interaction -> Dispatch Action -> new state (reduce) -> view rendered by presenter
3935

4036
## "Dumb Views"
41-
Views in this arch are truely 'dumb' - they should contain nearly no logic. They are responsible for rendering the view based on the `ViewState` given to them by the presenter. They are implemented for each platform and utilize native UI SDKs and libs for each platform. Android uses Fragments and iOS uses UIViewControllers.
37+
Views in this arch are truly 'dumb' - they should contain nearly no logic. They are responsible for rendering the view based on the `ViewState` given to them by the presenter. They are implemented for each platform and utilize native UI SDKs and libs for each platform. Android uses Fragments and iOS uses UIViewControllers.
4238

4339
## Presenters
4440

@@ -52,9 +48,10 @@ Presenters give a layer of control between subscribing to the new state and the
5248
![arch diagram](https://storage.googleapis.com/treestorage/Kotlin%20MPP%20Demo%20Arch.png)
5349

5450
## Async Actions
55-
In redux world there are many ways to handle creation of async actions. `Thunks` have been used in this app. `NetworkThunks` both use coroutines to launch concurrent operations that dispatch actions.
51+
In the redux world there are many ways to handle creation of async actions. `Thunks` have been used in this app. `NetworkThunks` both use coroutines to launch concurrent operations that dispatch actions.
5652

5753
## Navigation
58-
In this app, Navigation is considered a side effect of the AppState. The `NavigationMiddleware` handles changing screens based on dispatched actions. The `NavigationMiddleware` takes an implementation of `Navigator` which is implemeneted for each platform.
59-
54+
In this app, Navigation is considered a side effect of the AppState. The `NavigationMiddleware` handles changing screens based on dispatched actions. The `NavigationMiddleware` takes an implementation of `Navigator` which is implemented for each platform.
6055

56+
#Tests
57+
Unforntunately ran out of time and do not have tests. Unit tests can be written in the common module and ran in JVM and native. Reducers are very simple to tests and quick to run.

android/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ dependencies {
7373
implementation project(':common')
7474
implementation 'nl.dionsegijn:konfetti:1.1.2'
7575
implementation "com.russhwolf:multiplatform-settings:$multiplatformSettingsVersion"
76+
implementation "com.squareup.sqldelight:android-driver:$sqldelightVersion"
77+
7678

7779
kapt 'com.github.bumptech.glide:compiler:4.8.0'
7880
testImplementation 'junit:junit:4.12'

android/google-services.json

Lines changed: 0 additions & 48 deletions
This file was deleted.

android/src/androidTest/java/io/jackson/instacopy/ExampleInstrumentedTest.kt

Lines changed: 0 additions & 24 deletions
This file was deleted.

android/src/main/AndroidManifest.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,18 @@
1111
android:label="@string/app_name"
1212
android:roundIcon="@mipmap/ic_launcher_round"
1313
android:supportsRtl="true"
14-
android:theme="@style/NameGameAppTheme">
14+
android:theme="@style/LibraryAppTheme">
1515
<activity
1616
android:name=".MainActivity"
1717
android:label="@string/app_name"
18-
android:screenOrientation="portrait"
19-
android:theme="@style/NameGameAppTheme">
18+
android:theme="@style/LibraryAppTheme">
2019
<intent-filter>
2120
<action android:name="android.intent.action.MAIN" />
2221

2322
<category android:name="android.intent.category.LAUNCHER" />
2423
</intent-filter>
2524
</activity>
25+
<activity android:name=".DetailsActivity"/>
2626
</application>
2727

2828
</manifest>

android/src/main/java/com/jackson/openlibrary/AndroidNavigator.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.jackson.openlibrary
22

33
import android.app.Activity
44
import android.app.Application
5+
import android.content.Intent
56
import android.os.Bundle
67
import androidx.appcompat.app.AppCompatActivity
78
import androidx.navigation.findNavController
@@ -26,11 +27,11 @@ class AndroidNavigator : Navigator, Application.ActivityLifecycleCallbacks {
2627
if (currentActivity == null) {
2728
cachedNavigationScreen = screen
2829
} else {
29-
val navController = currentActivity!!.findNavController(R.id.nav_host_fragment)
30+
// val navController = currentActivity!!.findNavController(R.id.nav_host_fragment)
3031
when (screen) {
31-
Screen.QUESTION -> navController.navigate(R.id.action_startScreen_to_questionScreen)
32-
Screen.GAME_COMPLETE -> navController.navigate(R.id.action_questionScreen_to_resultsFragment)
33-
Screen.START -> navController.navigate(R.id.startScreen)
32+
// Screen.QUESTION -> navController.navigate(R.id.action_startScreen_to_questionScreen)
33+
// Screen.GAME_COMPLETE -> navController.navigate(R.id.action_questionScreen_to_resultsFragment)
34+
Screen.BOOK_DETAILS -> currentActivity?.startActivity(Intent(currentActivity, DetailsActivity::class.java))
3435
// Screen.START -> navController.navigate(R.id.action_resultsFragment_to_startScreen)
3536
else -> throw IllegalArgumentException("Screen $screen is not handled in AndroidNavigator")
3637
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.jackson.openlibrary
2+
3+
import android.os.Bundle
4+
import androidx.appcompat.app.AppCompatActivity
5+
6+
class DetailsActivity : AppCompatActivity() {
7+
8+
interface IOnBackPressed {
9+
fun onBackPressed(): Boolean
10+
}
11+
12+
override fun onCreate(savedInstanceState: Bundle?) {
13+
super.onCreate(savedInstanceState)
14+
setContentView(R.layout.activity_details)
15+
}
16+
17+
override fun onBackPressed() {
18+
val navHostFragment =
19+
this.supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
20+
val currentFragment = navHostFragment?.childFragmentManager?.fragments?.get(0)
21+
if (currentFragment is IOnBackPressed)
22+
(currentFragment as IOnBackPressed).onBackPressed()
23+
super.onBackPressed()
24+
}
25+
}

android/src/main/java/com/jackson/openlibrary/MainActivity.kt

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,59 @@ package com.jackson.openlibrary
22

33
import android.os.Bundle
44
import android.os.Handler
5+
import android.view.MenuItem
56
import android.view.MotionEvent
67
import android.view.View
78
import android.view.ViewConfiguration
89
import androidx.appcompat.app.AppCompatActivity
10+
import androidx.fragment.app.Fragment
911
import com.bumptech.glide.annotation.GlideModule
1012
import com.bumptech.glide.module.AppGlideModule
13+
import com.jackson.openlibrary.store.CompletedFragment
14+
import com.jackson.openlibrary.store.SearchFragment
15+
import com.jackson.openlibrary.store.ToReadFragment
1116
import com.willowtreeapps.hyperion.core.Hyperion
12-
import kotlinx.android.synthetic.main.app_bar_main.*
1317
import kotlinx.android.synthetic.main.activity_main.*
18+
import java.lang.IllegalArgumentException
1419

1520

1621
@GlideModule
1722
class MyAppGlideModule : AppGlideModule()
1823

1924
class MainActivity : AppCompatActivity() {
2025

21-
companion object {
22-
const val RECORD_REQUEST_CODE = 52
23-
}
24-
2526
interface IOnBackPressed {
2627
fun onBackPressed(): Boolean
2728
}
2829

2930
override fun onCreate(savedInstanceState: Bundle?) {
3031
super.onCreate(savedInstanceState)
31-
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN.or(View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
3232
setContentView(R.layout.activity_main)
33-
scrimLayout.setOnInsetsCallback {
34-
rootContent.setPadding(0, -it.top, 0, 0)
33+
btmNavigation.setOnNavigationItemSelectedListener(::handleBtmNavTap)
34+
btmNavigation.selectedItemId = R.id.toRead
35+
}
36+
37+
private fun handleBtmNavTap(it: MenuItem): Boolean {
38+
val fragment: Fragment = when (it.itemId) {
39+
R.id.toRead -> {
40+
supportFragmentManager.findFragmentByTag(ToReadFragment::class.java.name) ?: ToReadFragment()
41+
}
42+
R.id.completed -> {
43+
supportFragmentManager.findFragmentByTag(CompletedFragment::class.java.name) ?: CompletedFragment()
44+
}
45+
R.id.search -> {
46+
supportFragmentManager.findFragmentByTag(SearchFragment::class.java.name) ?: SearchFragment()
47+
}
48+
else -> throw IllegalArgumentException("Unhandled itemId in BottomNav $it")
3549
}
36-
drawer_layout.setOnTouchListener(tripleTapDetector)
3750

51+
val fragTransaction = supportFragmentManager.beginTransaction()
52+
fragTransaction.replace(R.id.nav_host_fragment, fragment, fragment::class.java.name)
53+
fragTransaction.commit()
54+
55+
return true
3856
}
3957

40-
// override fun onSupportNavigateUp(): Boolean = Navigation.findNavController(this, R.id.nav_host_fragment).navigateUp()
4158

4259
override fun onBackPressed() {
4360
val navHostFragment =

android/src/main/java/com/jackson/openlibrary/OpenLibraryApp.kt

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,54 @@ package com.jackson.openlibrary
33
import android.app.Activity
44
import android.app.Application
55
import android.os.Bundle
6+
import androidx.sqlite.db.SupportSQLiteDatabase
7+
import androidx.sqlite.db.SupportSQLiteOpenHelper
8+
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
69
import com.google.firebase.FirebaseApp
7-
import com.willowtreeapps.common.GameEngine
10+
import com.squareup.sqldelight.android.AndroidSqliteDriver
11+
import com.willowtree.common.LibraryDatabase
12+
import com.willowtreeapps.common.LibraryApp
813
import com.willowtreeapps.common.Logger
914
import kotlinx.coroutines.Dispatchers
1015

1116
class OpenLibraryApp : Application() {
1217

13-
lateinit var gameEngine: GameEngine
18+
lateinit var libraryApp: LibraryApp
1419

1520
override fun onCreate() {
1621
super.onCreate()
1722
FirebaseApp.initializeApp(this)
1823
instance = this
19-
val navigator = AndroidNavigator()
20-
gameEngine = GameEngine(navigator, this, Dispatchers.IO, Dispatchers.Main)
24+
val config = SupportSQLiteOpenHelper.Configuration.builder(this)
25+
.name("database.db")
26+
.callback(object : SupportSQLiteOpenHelper.Callback(1) {
27+
override fun onCreate(db: SupportSQLiteDatabase) {
28+
val driver = AndroidSqliteDriver(db)
29+
LibraryDatabase.Schema.create(driver)
30+
}
31+
32+
override fun onUpgrade(db: SupportSQLiteDatabase?, oldVersion: Int, newVersion: Int) {
33+
}
34+
35+
})
36+
.build()
2137

38+
val sqlHelper = FrameworkSQLiteOpenHelperFactory().create(config)
39+
40+
val navigator = AndroidNavigator()
41+
libraryApp = LibraryApp(navigator, Dispatchers.IO, Dispatchers.Main, AndroidSqliteDriver(sqlHelper))
2242
registerActivityLifecycleCallbacks(navigator)
2343
registerActivityLifecycleCallbacks(LifeCycleLogger)
2444
}
2545

2646
companion object {
2747
lateinit var instance: OpenLibraryApp
2848

29-
fun gameEngine() = instance.gameEngine
49+
fun gameEngine() = instance.libraryApp
3050
}
3151
}
3252

33-
object LifeCycleLogger: Application.ActivityLifecycleCallbacks {
53+
object LifeCycleLogger : Application.ActivityLifecycleCallbacks {
3454
override fun onActivityPaused(activity: Activity?) {
3555
Logger.d(activity?.javaClass?.simpleName + ": onActivityPaused")
3656
}

0 commit comments

Comments
 (0)