Skip to content

Commit 1907a90

Browse files
author
Patrick Jackson
committed
ToRead and completed working with database
1 parent 889b686 commit 1907a90

File tree

130 files changed

+368
-4630
lines changed

Some content is hidden

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

130 files changed

+368
-4630
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: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
<activity
1616
android:name=".MainActivity"
1717
android:label="@string/app_name"
18-
android:screenOrientation="portrait"
1918
android:theme="@style/LibraryAppTheme">
2019
<intent-filter>
2120
<action android:name="android.intent.action.MAIN" />

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

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,7 @@
11
package com.jackson.openlibrary
22

33
import android.os.Bundle
4-
import android.os.Handler
5-
import android.view.MenuItem
6-
import android.view.MotionEvent
7-
import android.view.View
8-
import android.view.ViewConfiguration
94
import androidx.appcompat.app.AppCompatActivity
10-
import androidx.fragment.app.Fragment
11-
import com.bumptech.glide.annotation.GlideModule
12-
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
16-
import com.willowtreeapps.hyperion.core.Hyperion
17-
import kotlinx.android.synthetic.main.activity_details.*
18-
import kotlinx.android.synthetic.main.app_bar_main.*
19-
import kotlinx.android.synthetic.main.activity_main.*
20-
import java.lang.IllegalArgumentException
215

226
class DetailsActivity : AppCompatActivity() {
237

@@ -28,7 +12,6 @@ class DetailsActivity : AppCompatActivity() {
2812
override fun onCreate(savedInstanceState: Bundle?) {
2913
super.onCreate(savedInstanceState)
3014
setContentView(R.layout.activity_details)
31-
setSupportActionBar(toolbar)
3215
}
3316

3417
override fun onBackPressed() {

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import com.jackson.openlibrary.store.CompletedFragment
1414
import com.jackson.openlibrary.store.SearchFragment
1515
import com.jackson.openlibrary.store.ToReadFragment
1616
import com.willowtreeapps.hyperion.core.Hyperion
17-
import kotlinx.android.synthetic.main.app_bar_main.*
1817
import kotlinx.android.synthetic.main.activity_main.*
1918
import java.lang.IllegalArgumentException
2019

@@ -30,7 +29,6 @@ class MainActivity : AppCompatActivity() {
3029

3130
override fun onCreate(savedInstanceState: Bundle?) {
3231
super.onCreate(savedInstanceState)
33-
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN.or(View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
3432
setContentView(R.layout.activity_main)
3533
btmNavigation.setOnNavigationItemSelectedListener(::handleBtmNavTap)
3634
btmNavigation.selectedItemId = R.id.toRead
@@ -39,19 +37,19 @@ class MainActivity : AppCompatActivity() {
3937
private fun handleBtmNavTap(it: MenuItem): Boolean {
4038
val fragment: Fragment = when (it.itemId) {
4139
R.id.toRead -> {
42-
ToReadFragment()
40+
supportFragmentManager.findFragmentByTag(ToReadFragment::class.java.name) ?: ToReadFragment()
4341
}
4442
R.id.completed -> {
45-
CompletedFragment()
43+
supportFragmentManager.findFragmentByTag(CompletedFragment::class.java.name) ?: CompletedFragment()
4644
}
4745
R.id.search -> {
48-
SearchFragment()
46+
supportFragmentManager.findFragmentByTag(SearchFragment::class.java.name) ?: SearchFragment()
4947
}
5048
else -> throw IllegalArgumentException("Unhandled itemId in BottomNav $it")
5149
}
5250

5351
val fragTransaction = supportFragmentManager.beginTransaction()
54-
fragTransaction.replace(R.id.nav_host_fragment, fragment)
52+
fragTransaction.replace(R.id.nav_host_fragment, fragment, fragment::class.java.name)
5553
fragTransaction.commit()
5654

5755
return true

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
}

android/src/main/java/com/jackson/openlibrary/store/BaseLibraryViewFragment.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import kotlinx.coroutines.CoroutineScope
1010
import kotlinx.coroutines.Dispatchers
1111
import kotlin.coroutines.CoroutineContext
1212

13-
open class BaseLibraryViewFragment<TPresenter: Presenter<*>>: Fragment(), CoroutineScope, View<TPresenter> {
13+
open class BaseLibraryViewFragment<TPresenter: Presenter<*>?>: Fragment(), CoroutineScope, View<TPresenter?> {
1414
override val coroutineContext: CoroutineContext
1515
get() = Dispatchers.Main
1616

17-
override lateinit var presenter: TPresenter
17+
override var presenter: TPresenter? = null
1818
private var viewRecreated: Boolean = false
1919

2020
override fun onViewCreated(view: android.view.View, savedInstanceState: Bundle?) {
@@ -31,7 +31,7 @@ open class BaseLibraryViewFragment<TPresenter: Presenter<*>>: Fragment(), Corout
3131
super.onResume()
3232
OpenLibraryApp.gameEngine().attachView(this)
3333
if (viewRecreated) {
34-
presenter.recreateView()
34+
presenter?.recreateView()
3535
}
3636
}
3737

0 commit comments

Comments
 (0)