MangaKu
🤖 Introduction
MangaKu App Powered by Kotlin Multiplatform Mobile, Jetpack Compose, and SwiftUI
Module
shared
: data and domain layermangaku-ios
: ios presentation layermangaku-android
: android presentation layerbuildSrc
:mangaku-android
andshared
dependencies
Table of Contents
- Introduction
- Features
- Installation
- Screenshot
- Libraries
- Domain to Presentation
- Expect and Actual
- Project Structure
🦾 Features
A few things you can do with MangaKu:
- View Popular Manga
- Easily search for any Manga
- See Manga Detail
- Save your favorite manga
This project have no concern about backward compatibility, and only support the very latest or experimental api's for both android and ios
🚗 Installation
- Follow the KMM Guide by Jetbrains for getting started building a project with KMM.
- Install Kotlin Multiplatform Mobile plugin in Android Studio
- Clone or download the repo
- Rebuild Project
- To run in iOS, Open Xcode and
pod install
insidemangaku-ios
folder to install shared module and ios dependencies
📸 Screenshot
💡 Libraries
shared
:
mangaku-ios
:
mangaku-android
:
- Jetpack Compose
- Accompanist
- Koin
- Compose Destinations
- Some Kotlinx & Jetpack Components
💨 Domain to Presentation
In Android, Because both shared
and mangaku-android
written in Kotlin, we can simply collect flow :
private fun getTrendingManga() = viewModelScope.launch {
_trendingManga.value = Result.loading()
browseUseCase.getManga()
.catch { cause: Throwable ->
_trendingManga.value = Result.failed(cause)
}
.collect { result ->
if (result.isNotEmpty())
_trendingManga.value = Result.success(result)
}
}
But in iOS, we have to deal with swift, here i'm using createPublisher()
from KMPNativeCoroutines
to collect flow as Publisher in Combine
:
func fetchTrendingManga() {
trendingManga = .loading
createPublisher(for: browseUseCase.getTrendingMangaNative())
.receive(on: DispatchQueue.main)
.sink { completion in
switch completion {
case .finished: ()
case .failure(let error):
self.trendingManga = .error(error: error)
}
} receiveValue: { value in
self.trendingManga = .success(data: value)
}.store(in: &cancellables)
}
or even better, you can use asyncFunction
/ asyncResult
/ asyncStream
function to collect coroutine flow as new swift's concurrency features, checkout branch feat/experimenting-swift-new concurrency to see the example
combining two powerful concurrency feature from both native framework, how cool is that !?
func fetchTrendingManga() {
Task {
trendingManga = .loading
do {
let nativeFlow = try await asyncFunction(for: browseUseCase.getTrendingMangaNative())
let stream = asyncStream(for: nativeFlow)
for try await data in stream {
trendingManga = .success(data: data)
}
} catch {
trendingManga = .error(error: error)
}
}
}
learn more: https://github.com/rickclephas/KMP-NativeCoroutines
🚀 Expect and Actual
in KMM, there is a negative case when there's no support to share code for some feature in both ios and android, and it's expensive to write separately in each module
so the solution is ✨expect
and actual
✨, we can write expect
inside commonMain
and write "actual" implementation with actual
inside androidMain
and iosMain
and then each module will use expect
example:
commonMain/utils/DateFormatter.kt
expect fun formatDate(dateString: String, format: String): String
androidMain/utils/DateFormatter.kt
SimpleDateFormat
actual fun formatDate(dateString: String, format: String): String {
val date = SimpleDateFormat(Constants.formatFromApi).parse(dateString)
val dateFormatter = SimpleDateFormat(format, Locale.getDefault())
return dateFormatter.format(date ?: Date())
}
iosMain/utils/DateFormatter.kt
NSDateFormatter
actual fun formatDate(dateString: String, format: String): String {
val dateFormatter = NSDateFormatter().apply {
dateFormat = Constants.formatFromApi
}
val formatter = NSDateFormatter().apply {
dateFormat = format
locale = NSLocale(localeIdentifier = "id_ID")
}
return formatter.stringFromDate(dateFormatter.dateFromString(dateString) ?: NSDate())
}
yes, we can use Foundation
same as what we use in Xcode
☕️ Buy Me a Coffee
If you like this project please support me by ;-)
🏛 Project Structure
shared
:
data
mapper
repository
source
local
entity
remote
response
di
domain
model
repository
usecase
browse
detail
mymanga
search
utils
mangaku-android
:
ui
composables
home
composables
favorite
search
detail
di
utils
mangaku-ios
:
Dependency
App
Main
Resources
ReusableView
Extensions
Utils
Features
Browse
Navigator
Views
Search
Detail
MyManga