Skip to content

Commit

Permalink
Add domain for user profile (#62)
Browse files Browse the repository at this point in the history
* Add domain for user profile

* Add Supplier to AndroidApplicationService.
Remove demo code from MainNodePresenter and inti code to AndroidApplicationService.
Add generateKeyPairInProgress and createAndPublishInProgress properties.
Add createSimulatedDelay method to ClientUserProfileServiceFacade

Supplier is then used in DI and provides the services from the AndroidApplicationService when used by a lazy getter.
This hack is needed as AndroidApplicationService requires some constructor argumewnts which are not present as application startup but only after the mainActiviy is created.
We startup KOIN in the MainApplication.onCreate method.
We use the MainNodePresenter which gets called its onViewAttached once the mainActiviy is ready and then we can set the applicationService inside the supplier.

I don't like that unsafe lateinit setter strategy and have proposed an alterantive solution in my POC which would work with constructor injection. Maybe there are some hacks in KOIN to get the done in a different manner, but the main problem is that KOIN gets started at the MainApplication.onCreate and not on the MainActivity.onCreate. Only at that moment we have all the basic dependencies from the Android UI framework.
But even if we would move that to the MainActivity.onCreate, we would need to pass the properties to the module in some way.

* Rename AndroidModule to AndroidClientModule
Add DI config to AndroidClientModule
Remove ICreateProfilePresenter as there is only one instance and not expected a use case for multiple instances.
Add UserProfileRepository holding model and serviceFacade
Adjust Screen and Presenter.

Discussion:
To me the usage of the repository does not make much sense here.
The 2 relevant methods do not map into the CRUD pattern which seems to be the design philosophy for repositories.
I would suggest to use it only when teh CRUD pattern and persistence is used and appropriate. And to inject the model and the service facade to the presenter in the other cases.
Otherwise we just create a useless wrapper.
@rodvar: If you would implement the usage here in a different way, can you provide with the given use case an implementation example?

* Add "android.permission.INTERNET" entry to manifest for Android client

* Fix parameter in ApiRequestService. We pass the host instead of baseUrl.

On iOS simulator we need to use localhost, not 10.0.2.2

* Add IosClientModule
Use Dispatchers.Main instead of Dispatchers.IO as it seems iOS does not support that.

* Add equals and hashCode

* Remove redundant config for UserProfileRepository

* Remove unused model

* Add pruneAllBackups and readAllPersisted calls in AndroidApplicationService

* Add applySelectedUserProfile, getSelectedUserProfile and getUserIdentityIds methods.

In the CreateProfilePresenter at onLaunched there are commented out some different dev test modes;
- Use hasUserProfile to check if create user profile screen should be shown
- onGenerateKeyPair if no profile is present
- applySelectedUserProfile if one is present and we apply the data to the screen. This is only for dev testing. We have to display the selected user profile in the app in a TBD way.

Tested with all 3 apps and working.
Requires latest Bisq 2 main version and a running rest api.

* Move gradle dependencies to toml file

* Fix merge issue

* Fix merge issue
Go to next screen on onCreateAndPublishNewUserProfile completed.

* Move bisq2 compatible models to client.replicated_model

* Add clientModule, use DI for HttpClient

* Use class name for Logger.withTag
Remove unneeded coroutineScope.launch blocks

* Add ignoreUnknownKeys to json deserializer
Make Json  configs fields

* Add serviceFacade and model for bootstrap

* Refactor: Rename MainNodePresenter to NodeMainPresenter

* Add  serviceFacade and model for application bootstrap.

This only provides the domain part. It is not yet connected to the UI as its still unclear to me what patter to use for that.

When using the Client version one need to instantiate th ClientApplicationBootstrapFacade and call initialize(). Could be done in the presenter...

* Remove domain models and use return values instead

* Nits contribution

 - fix double call on view attached for the special case of the
   MainPresenter
 - remove double static configuration of bisq core jars (left in
   MainApplication override only)
 - provide single instance of android memory report service and adjust
   usages
 - cleanup/comment dead code
 - cleanup unused imports

* Apply code review suggestions

* Adjust to API changes

---------

Co-authored-by: Rodrigo Varela <[email protected]>
  • Loading branch information
HenrikJannsen and rodvar authored Nov 21, 2024
1 parent 6e8839b commit 83cc9bc
Show file tree
Hide file tree
Showing 49 changed files with 1,030 additions and 681 deletions.
5 changes: 5 additions & 0 deletions bisqapps/androidClient/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ android {
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
excludes.add("META-INF/LICENSE.md")
excludes.add("META-INF/NOTICE.md")
excludes.add("META-INF/NOTICE.markdown")
}
}
buildTypes {
Expand All @@ -82,6 +85,8 @@ android {
dependencies {
implementation(project(":shared:presentation"))
implementation(project(":shared:domain"))
// FIXME hack to avoid the issue that org.slf4j is not found as we exclude it in shared
implementation(libs.ktor.client.cio)
debugImplementation(compose.uiTooling)
}

2 changes: 2 additions & 0 deletions bisqapps/androidClient/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".MainApplication"
android:allowBackup="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.koin.android.ext.android.inject

class MainActivity : ComponentActivity() {
private val presenter: MainPresenter by inject()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
presenter.attachView(this)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package network.bisq.mobile.client

import android.app.Application
import network.bisq.mobile.client.di.androidModule
import network.bisq.mobile.client.di.androidClientModule
import network.bisq.mobile.client.di.clientModule
import network.bisq.mobile.domain.di.domainModule
import network.bisq.mobile.presentation.di.presentationModule
import org.koin.android.ext.koin.androidContext
Expand All @@ -14,7 +15,7 @@ class MainApplication: Application() {

startKoin {
androidContext(this@MainApplication)
modules(listOf(domainModule, presentationModule, androidModule))
modules(listOf(domainModule, presentationModule, clientModule, androidClientModule))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package network.bisq.mobile.client.di

import network.bisq.mobile.android.node.main.bootstrap.ClientApplicationBootstrapFacade
import network.bisq.mobile.client.presentation.AndroidClientMainPresenter
import network.bisq.mobile.client.service.ApiRequestService
import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileServiceFacade
import network.bisq.mobile.domain.client.main.user_profile.UserProfileApiGateway
import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
import network.bisq.mobile.presentation.MainPresenter
import network.bisq.mobile.presentation.ui.AppPresenter
import org.koin.dsl.bind
import org.koin.dsl.module

val androidClientModule = module {
single<ApplicationBootstrapFacade> { ClientApplicationBootstrapFacade() }

single { ApiRequestService(get(), "10.0.2.2") }
single { UserProfileApiGateway(get()) }
single<UserProfileServiceFacade> { ClientUserProfileServiceFacade(get()) }
single<MainPresenter> { AndroidClientMainPresenter(get()) } bind AppPresenter::class
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package network.bisq.mobile.client.presentation

import network.bisq.mobile.domain.data.repository.GreetingRepository
import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.presentation.MainPresenter

@Suppress("UNCHECKED_CAST")
class AndroidClientMainPresenter(
private val applicationBootstrapFacade: ApplicationBootstrapFacade
) : MainPresenter(GreetingRepository()) {
var applicationServiceInited = false
override fun onViewAttached() {
super.onViewAttached()

if (!applicationServiceInited) {
applicationServiceInited = true
applicationBootstrapFacade.initialize()
}
}
}
10 changes: 8 additions & 2 deletions bisqapps/androidNode/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@

import com.google.protobuf.gradle.proto
import org.apache.tools.ant.taskdefs.condition.Os
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.apache.tools.ant.taskdefs.condition.Os
import com.google.protobuf.gradle.proto

plugins {
alias(libs.plugins.kotlinMultiplatform)
Expand Down Expand Up @@ -81,6 +82,10 @@ android {
// Exclude the conflicting META-INF files
excludes.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF")
excludes.add("META-INF/DEPENDENCIES")
excludes.add("META-INF/LICENSE.md")
excludes.add("META-INF/NOTICE.md")
excludes.add("META-INF/INDEX.LIST")
excludes.add("META-INF/NOTICE.markdown")
pickFirsts.add("**/protobuf/**/*.class")
}
}
Expand Down Expand Up @@ -161,6 +166,7 @@ dependencies {

implementation(libs.koin.core)
implementation(libs.koin.android)
implementation(libs.logging.kermit)
}

// ensure tests run on J17
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package network.bisq.mobile.android.node.service
package network.bisq.mobile.android.node

import androidx.core.util.Supplier
import bisq.account.AccountService
import bisq.application.ApplicationService
import bisq.application.State
Expand All @@ -39,7 +40,9 @@ import bisq.trade.TradeService
import bisq.user.UserService
import com.google.common.base.Preconditions
import lombok.Getter
import lombok.Setter
import lombok.extern.slf4j.Slf4j
import network.bisq.mobile.android.node.service.AndroidMemoryReportService
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.nio.file.Path
Expand All @@ -55,12 +58,52 @@ import java.util.concurrent.TimeUnit
*/
@Slf4j
@Getter
class AndroidApplicationService(androidMemoryService: AndroidMemoryReportService, userDataDir: Path?) :
class AndroidApplicationService(androidMemoryReportService: AndroidMemoryReportService, userDataDir: Path?) :
ApplicationService("android", arrayOf<String>(), userDataDir) {

@Getter
class Supplier {
@Setter
lateinit var applicationService: AndroidApplicationService
var stateSupplier: androidx.core.util.Supplier<Observable<State>> =
Supplier { applicationService.state }
var securityServiceSupplier: androidx.core.util.Supplier<SecurityService> =
Supplier { applicationService.securityService }
var networkServiceSupplier: androidx.core.util.Supplier<NetworkService> =
Supplier { applicationService.networkService }
var identityServiceSupplier: androidx.core.util.Supplier<IdentityService> =
Supplier { applicationService.identityService }
var bondedRolesServiceSupplier: androidx.core.util.Supplier<BondedRolesService> =
Supplier { applicationService.bondedRolesService }
var accountServiceSupplier: androidx.core.util.Supplier<AccountService> =
Supplier { applicationService.accountService }
var offerServiceSupplier: androidx.core.util.Supplier<OfferService> =
Supplier { applicationService.offerService }
var contractServiceSupplier: androidx.core.util.Supplier<ContractService> =
Supplier { applicationService.contractService }
var userServiceSupplier: androidx.core.util.Supplier<UserService> =
Supplier { applicationService.userService }
var chatServiceSupplier: androidx.core.util.Supplier<ChatService> =
Supplier { applicationService.chatService }
var settingsServiceSupplier: androidx.core.util.Supplier<SettingsService> =
Supplier { applicationService.settingsService }
var supportServiceSupplier: androidx.core.util.Supplier<SupportService> =
Supplier { applicationService.supportService }
var systemNotificationServiceSupplier: androidx.core.util.Supplier<SystemNotificationService> =
Supplier { applicationService.systemNotificationService }
var tradeServiceSupplier: androidx.core.util.Supplier<TradeService> =
Supplier { applicationService.tradeService }
var alertNotificationsServiceSupplier: androidx.core.util.Supplier<AlertNotificationsService> =
Supplier { applicationService.alertNotificationsService }
var favouriteMarketsServiceSupplier: androidx.core.util.Supplier<FavouriteMarketsService> =
Supplier { applicationService.favouriteMarketsService }
var dontShowAgainServiceSupplier: androidx.core.util.Supplier<DontShowAgainService> =
Supplier { applicationService.dontShowAgainService }
}

companion object {
const val STARTUP_TIMEOUT_SEC: Long = 300
const val SHUTDOWN_TIMEOUT_SEC: Long = 10
private val INSTANCE: AndroidApplicationService? = null
val log: Logger = LoggerFactory.getLogger(ApplicationService::class.java)
}

Expand All @@ -70,6 +113,7 @@ class AndroidApplicationService(androidMemoryService: AndroidMemoryReportService

val securityService =
SecurityService(persistenceService, SecurityService.Config.from(getConfig("security")))

val networkService = NetworkService(
NetworkServiceConfig.from(
config.baseDir,
Expand All @@ -79,9 +123,9 @@ class AndroidApplicationService(androidMemoryService: AndroidMemoryReportService
securityService.keyBundleService,
securityService.hashCashProofOfWorkService,
securityService.equihashProofOfWorkService,
androidMemoryService
androidMemoryReportService
)
private val identityService = IdentityService(
val identityService = IdentityService(
persistenceService,
securityService.keyBundleService,
networkService
Expand All @@ -91,9 +135,9 @@ class AndroidApplicationService(androidMemoryService: AndroidMemoryReportService
getPersistenceService(),
networkService
)
private val accountService = AccountService(persistenceService)
private val offerService = OfferService(networkService, identityService, persistenceService)
private val contractService = ContractService(securityService)
val accountService = AccountService(persistenceService)
val offerService = OfferService(networkService, identityService, persistenceService)
val contractService = ContractService(securityService)
val userService = UserService(
persistenceService,
securityService,
Expand All @@ -103,12 +147,12 @@ class AndroidApplicationService(androidMemoryService: AndroidMemoryReportService
)
val chatService: ChatService
val settingsService = SettingsService(persistenceService)
private val supportService: SupportService
private val systemNotificationService = SystemNotificationService(Optional.empty())
private val tradeService: TradeService
private val alertNotificationsService: AlertNotificationsService
private val favouriteMarketsService: FavouriteMarketsService
private val dontShowAgainService: DontShowAgainService
val supportService: SupportService
val systemNotificationService = SystemNotificationService(Optional.empty())
val tradeService: TradeService
val alertNotificationsService: AlertNotificationsService
val favouriteMarketsService: FavouriteMarketsService
val dontShowAgainService: DontShowAgainService


init {
Expand Down Expand Up @@ -152,8 +196,16 @@ class AndroidApplicationService(androidMemoryService: AndroidMemoryReportService
}

override fun initialize(): CompletableFuture<Boolean> {
var ts = System.currentTimeMillis()
pruneAllBackups().join()
log.info("pruneAllBackups took {} ms", System.currentTimeMillis() - ts)

ts = System.currentTimeMillis()
readAllPersisted().join()
log.info("readAllPersisted took {} ms", System.currentTimeMillis() - ts)

return securityService.initialize()
.thenCompose<Boolean> { result: Boolean? ->
.thenCompose { result: Boolean? ->
setState(State.INITIALIZE_NETWORK)
networkService.initialize()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package network.bisq.mobile.android.node.di

import bisq.security.SecurityService
import bisq.user.identity.UserIdentityService
import network.bisq.mobile.android.node.AndroidApplicationService
import network.bisq.mobile.android.node.domain.bootstrap.NodeApplicationBootstrapFacade
import network.bisq.mobile.android.node.domain.data.repository.NodeGreetingRepository
import network.bisq.mobile.android.node.presentation.MainNodePresenter
import network.bisq.mobile.android.node.service.AndroidApplicationService
import network.bisq.mobile.android.node.domain.user_profile.NodeUserProfileServiceFacade
import network.bisq.mobile.android.node.presentation.NodeMainPresenter
import network.bisq.mobile.android.node.service.AndroidMemoryReportService
import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
import network.bisq.mobile.presentation.MainPresenter
import network.bisq.mobile.presentation.ui.AppPresenter
import org.koin.android.ext.koin.androidContext
Expand All @@ -15,27 +17,19 @@ import org.koin.dsl.module
val androidNodeModule = module {
// this one is for example properties, will be eliminated soon
single<NodeGreetingRepository> { NodeGreetingRepository() }
// this line showcases both, the posibility to change behaviour of the app by changing one definiton

single<AndroidMemoryReportService> {
AndroidMemoryReportService(androidContext())
}

single { AndroidApplicationService.Supplier() }

single<ApplicationBootstrapFacade> { NodeApplicationBootstrapFacade(get()) }

single<UserProfileServiceFacade> { NodeUserProfileServiceFacade(get()) }


// this line showcases both, the possibility to change behaviour of the app by changing one definition
// and binding the same obj to 2 different abstractions
single<MainPresenter> { MainNodePresenter(get()) } bind AppPresenter::class

// Services
// TODO might not work because of the jars dependencies, needs more work
// single <AndroidMemoryReportService> {
// val context = androidContext()
// AndroidMemoryReportService(context)
// }
// single <AndroidApplicationService> {
// val filesDirsPath = androidContext().filesDir.toPath()
// val androidMemoryService: AndroidMemoryReportService = get()
// AndroidApplicationService(androidMemoryService, filesDirsPath)
// }
// single <UserIdentityService> {
// val applicationService: AndroidApplicationService = get()
// applicationService.userService.userIdentityService
// }
// single <SecurityService> {
// val applicationService: AndroidApplicationService = get()
// applicationService.securityService
// }
single<MainPresenter> { NodeMainPresenter(get(), get(), get(), get()) } bind AppPresenter::class
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package network.bisq.mobile.android.node.domain.bootstrap

import bisq.application.State
import network.bisq.mobile.android.node.AndroidApplicationService
import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade

class NodeApplicationBootstrapFacade(
private val supplier: AndroidApplicationService.Supplier
) :
ApplicationBootstrapFacade() {

override fun initialize() {
supplier.stateSupplier.get().addObserver { state: State ->
when (state) {
State.INITIALIZE_APP -> {
setState("Starting Bisq")
setProgress(0f)
}

State.INITIALIZE_NETWORK -> {
setState("Initialize P2P network")
setProgress(0.5f)
}

// not used
State.INITIALIZE_WALLET -> {
}

State.INITIALIZE_SERVICES -> {
setState("Initialize services")
setProgress(0.75f)
}

State.APP_INITIALIZED -> {
setState("Bisq started")
setProgress(1f)
}

State.FAILED -> {
setState("Startup failed")
setProgress(0f)
}
}
}
}
}
Loading

0 comments on commit 83cc9bc

Please sign in to comment.