Skip to content

Commit

Permalink
Implement sample navigation for both platforms
Browse files Browse the repository at this point in the history
  • Loading branch information
bpedryc committed Jun 7, 2023
1 parent f1cbc9f commit e36923d
Show file tree
Hide file tree
Showing 36 changed files with 462 additions and 131 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,6 @@ dependencies {
coreLibraryDesugaring(libs.android.desugaring)
implementation(libs.koin.android)
testImplementation(libs.junit)
implementation(libs.compose.navigation)
implementation(libs.koin.compose)
}
11 changes: 2 additions & 9 deletions app/src/main/kotlin/co/touchlab/kampkit/android/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,17 @@ package co.touchlab.kampkit.android
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import co.touchlab.kampkit.android.ui.MainScreen
import co.touchlab.kampkit.android.ui.MainNavigation
import co.touchlab.kampkit.android.ui.theme.KaMPKitTheme
import co.touchlab.kampkit.injectLogger
import co.touchlab.kampkit.models.BreedViewModel
import co.touchlab.kermit.Logger
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.component.KoinComponent

class MainActivity : ComponentActivity(), KoinComponent {

private val log: Logger by injectLogger("MainActivity")
private val viewModel: BreedViewModel by viewModel()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
KaMPKitTheme {
MainScreen(viewModel, log)
MainNavigation()
}
}
}
Expand Down
18 changes: 13 additions & 5 deletions app/src/main/kotlin/co/touchlab/kampkit/android/MainApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import co.touchlab.kampkit.AppInfo
import co.touchlab.kampkit.initKoin
import co.touchlab.kampkit.models.BreedViewModel
import co.touchlab.kampkit.core.AppInfo
import co.touchlab.kampkit.core.initKoin
import co.touchlab.kampkit.ui.breedDetails.BreedDetailsViewModel
import co.touchlab.kampkit.ui.breeds.BreedsViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.parameter.parametersOf
import org.koin.dsl.module
Expand All @@ -18,9 +19,16 @@ class MainApp : Application() {
initKoin(
module {
single<Context> { this@MainApp }
viewModel { BreedViewModel(get(), get { parametersOf("BreedViewModel") }) }
viewModel {
BreedsViewModel(get(), get { parametersOf("BreedsViewModel") })
}
viewModel {params ->
BreedDetailsViewModel(
params.get(), get(), get { parametersOf("BreedDetailsViewModel") }
)
}
single<SharedPreferences> {
get<Context>().getSharedPreferences("KAMPSTARTER_SETTINGS", Context.MODE_PRIVATE)
get<Context>().getSharedPreferences("KAMPSTARTER_SETTINGS", MODE_PRIVATE)
}
single<AppInfo> { AndroidAppInfo }
single {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package co.touchlab.kampkit.android.ui

import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.touchlab.kampkit.ui.breedDetails.BreedDetailsViewModel
import co.touchlab.kermit.Logger

@Composable
fun BreedDetailsScreen(
viewModel: BreedDetailsViewModel,
log: Logger
) {
val state by viewModel.detailsState.collectAsStateWithLifecycle()

Text("${state.breed?.name }")
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,36 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.touchlab.kampkit.android.R
import co.touchlab.kampkit.db.Breed
import co.touchlab.kampkit.models.BreedViewModel
import co.touchlab.kampkit.models.BreedViewState
import co.touchlab.kampkit.ui.breeds.BreedViewState
import co.touchlab.kampkit.ui.breeds.BreedsViewModel
import co.touchlab.kampkit.ui.breeds.NavigationIntent
import co.touchlab.kermit.Logger
import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState

@Composable
fun MainScreen(
viewModel: BreedViewModel,
fun BreedsScreen(
viewModel: BreedsViewModel,
onNavigateToDetails: (breedId: Long) -> Unit,
log: Logger
) {
val dogsState by viewModel.breedState.collectAsStateWithLifecycle()

dogsState.navigationIntent?.let { navIntent ->
LaunchedEffect(navIntent) {
if (navIntent is NavigationIntent.ToDetails) {
onNavigateToDetails(navIntent.breedId)
viewModel.onNavigationCompleted()
}
}
}

MainScreenContent(
dogsState = dogsState,
onRefresh = { viewModel.refreshBreeds() },
onSuccess = { data -> log.v { "View updating with ${data.size} breeds" } },
onError = { exception -> log.e { "Displaying error: $exception" } },
onFavorite = { viewModel.updateBreedFavorite(it) }
onFavorite = { viewModel.updateBreedFavorite(it) },
)
}

Expand All @@ -58,7 +69,7 @@ fun MainScreenContent(
onRefresh: () -> Unit = {},
onSuccess: (List<Breed>) -> Unit = {},
onError: (String) -> Unit = {},
onFavorite: (Breed) -> Unit = {}
onFavorite: (Breed) -> Unit = {},
) {
Surface(
color = MaterialTheme.colors.background,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package co.touchlab.kampkit.android.ui

import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import org.koin.androidx.compose.get
import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf

@Composable
fun MainNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "breeds") {
composable("breeds") {
BreedsScreen(
viewModel = koinViewModel(),
onNavigateToDetails = { breedId ->
navController.navigate("breedDetails/$breedId")
},
log = get { parametersOf("BreedsScreen") }
)
}
composable(
route = "breedDetails/{breedId}",
arguments = listOf(navArgument("breedId") { type = NavType.LongType })
) {
val breedId = it.arguments?.getLong("breedId")
BreedDetailsScreen(
viewModel = koinViewModel { parametersOf(breedId) },
log = get { parametersOf("BreedDetailsScreen") }
)
}
}
}
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ androidx-core = "1.9.0"
androidx-test-junit = "1.1.3"
androidx-activity-compose = "1.5.1"
androidx-lifecycle = "2.6.0"
androidx-navigation-compose = "2.5.3"

junit = "4.13.2"

Expand Down Expand Up @@ -51,6 +52,7 @@ compose-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "co
compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" }
compose-material = { module = "androidx.compose.material:material", version.ref = "compose" }
compose-activity = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" }
compose-navigation = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation-compose" }

coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
Expand All @@ -62,6 +64,7 @@ junit = { module = "junit:junit", version.ref = "junit" }
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin"}

kotlinx-dateTime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }

Expand Down
10 changes: 9 additions & 1 deletion ios/KaMPKitiOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
3C6AEC072A30881B0003F34A /* BreedDetailsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6AEC062A30881B0003F34A /* BreedDetailsScreen.swift */; };
3C6AEC092A30C17A0003F34A /* AppNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6AEC082A30C17A0003F34A /* AppNavigationController.swift */; };
3CD290EC2A251417004C7AD1 /* KMPNativeCoroutinesCombine in Frameworks */ = {isa = PBXBuildFile; productRef = 3CD290EB2A251417004C7AD1 /* KMPNativeCoroutinesCombine */; };
3CD290EE2A251417004C7AD1 /* KMPNativeCoroutinesCore in Frameworks */ = {isa = PBXBuildFile; productRef = 3CD290ED2A251417004C7AD1 /* KMPNativeCoroutinesCore */; };
3DFF917C64A18A83DA010EE1 /* Pods_KaMPKitiOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B859F3FB23133D22AB9DD835 /* Pods_KaMPKitiOS.framework */; };
Expand Down Expand Up @@ -40,6 +42,8 @@
/* Begin PBXFileReference section */
1DFCC00C8DAA719770A18D1A /* Pods-KaMPKitiOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KaMPKitiOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-KaMPKitiOS/Pods-KaMPKitiOS.release.xcconfig"; sourceTree = "<group>"; };
2A1ED6A4A2A53F5F75C58E5F /* Pods-KaMPKitiOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KaMPKitiOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-KaMPKitiOS/Pods-KaMPKitiOS.release.xcconfig"; sourceTree = "<group>"; };
3C6AEC062A30881B0003F34A /* BreedDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreedDetailsScreen.swift; sourceTree = "<group>"; };
3C6AEC082A30C17A0003F34A /* AppNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNavigationController.swift; sourceTree = "<group>"; };
46A5B5EE26AF54F7002EFEAA /* BreedListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreedListScreen.swift; sourceTree = "<group>"; };
46A5B60726B04920002EFEAA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
46B5284C249C5CF400A7725D /* Koin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Koin.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -138,6 +142,8 @@
F1465F0B23AA94BF0055F7C3 /* LaunchScreen.storyboard */,
F1465F0E23AA94BF0055F7C3 /* Info.plist */,
46A5B5EE26AF54F7002EFEAA /* BreedListScreen.swift */,
3C6AEC062A30881B0003F34A /* BreedDetailsScreen.swift */,
3C6AEC082A30C17A0003F34A /* AppNavigationController.swift */,
);
path = KaMPKitiOS;
sourceTree = "<group>";
Expand Down Expand Up @@ -364,6 +370,8 @@
46B5284D249C5CF400A7725D /* Koin.swift in Sources */,
46A5B5EF26AF54F7002EFEAA /* BreedListScreen.swift in Sources */,
F1465F0123AA94BF0055F7C3 /* AppDelegate.swift in Sources */,
3C6AEC072A30881B0003F34A /* BreedDetailsScreen.swift in Sources */,
3C6AEC092A30C17A0003F34A /* AppNavigationController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -453,7 +461,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Apple Development: [email protected] (94U525PPDD)";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6A5MWU525T;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
Expand Down
5 changes: 3 additions & 2 deletions ios/KaMPKitiOS/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

startKoin()

let viewController = UIHostingController(rootView: BreedListScreen())
let navController = AppNavigationController()
navController.toBreeds()

self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = viewController
self.window?.rootViewController = navController
self.window?.makeKeyAndVisible()

log.v(message: {"App Started"})
Expand Down
29 changes: 29 additions & 0 deletions ios/KaMPKitiOS/AppNavigationController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// AppNavigationController.swift
// KaMPKitiOS
//
// Created by Bartłomiej Pedryc on 07/06/2023.
// Copyright © 2023 Touchlab. All rights reserved.
//

import SwiftUI
import Foundation

class AppNavigationController: UINavigationController {
func toBreeds() {
pushViewController(
UIHostingController(
rootView: BreedListScreen(observableModel: ObservableBreedModel(navigationController: self))
),
animated: false
)
}
func toBreedDetails(breedId: Int64) {
pushViewController(
UIHostingController(
rootView: BreedDetailsScreen(observableModel: ObservableBreedDetailsModel(breedId: breedId))
),
animated: false
)
}
}
67 changes: 67 additions & 0 deletions ios/KaMPKitiOS/BreedDetailsScreen.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// BreedDetailsScreen.swift
// KaMPKitiOS
//
// Created by Bartłomiej Pedryc on 07/06/2023.
// Copyright © 2023 Touchlab. All rights reserved.
//

import Combine
import SwiftUI
import shared
import KMPNativeCoroutinesCombine
import Foundation

class ObservableBreedDetailsModel: ObservableObject {
private var viewModel: BreedDetailsViewModel

init(breedId: Int64) {
self.viewModel = KotlinDependencies.shared.getBreedDetailsViewModel(breedId: breedId)
}

@Published
var breed: Breed?

private var cancellables = [AnyCancellable]()

func activate() {
createPublisher(for: viewModel.detailsStateFlow)
.sink { _ in } receiveValue: { [weak self] (detailsState: BreedDetailsViewState) in
if let breed = detailsState.breed { self?.breed = breed }
}
.store(in: &cancellables)
}

func deactivate() {
cancellables.forEach { $0.cancel() }
cancellables.removeAll()
}

deinit {
viewModel.clear()
}
}

struct BreedDetailsScreen: View {
@StateObject
var observableModel: ObservableBreedDetailsModel

var body: some View {
BreedDetailsContent(
breedName: observableModel.breed?.name ?? ""
)
.onAppear(perform: {
observableModel.activate()
})
.onDisappear(perform: {
observableModel.deactivate()
})
}
}

struct BreedDetailsContent: View {
var breedName: String
var body: some View {
Text(breedName)
}
}
Loading

0 comments on commit e36923d

Please sign in to comment.