I have a app that checks when loggin in the type of user, to know where to navigate.
Whenver I do the login, in the logs, the realm will open 4 times.
My login View:
struct LoginView: View {
@State var userId : String = ""
@State var password : String = ""
@State var goToNextScreen : Int? = nil
@State var myIsUser = false
@State var myUser = UserInfo()
var repo = RealmRepo()
var body: some View {
NavigationView{
VStack(){
TextField("Username", text: $userId)
TextField("Password", text: $password)
Button("Login"){
repo.login(email: $userId.wrappedValue, password: $password.wrappedValue) { user, error in
if(user != nil){
self.repo.getUserProfile(userId: user?.id as! String).watch(block: {items in
self.myUser = items as! UserInfo
if(self.myUser.typeOfUser != true){
print("this user type 1")
UserDefaults.standard.set(false, forKey: "isUser")
UserDefaults.standard.set(true, forKey: "isLogin")
myIsUser=false
}else{
print("this is user type2")
UserDefaults.standard.set(true, forKey: "isUser")
UserDefaults.standard.set(true, forKey: "isLogin")
myIsUser=true
}
goToNextScreen=1
})
}else{
print("login failed")
}
}
}
NavigationLink(destination: myIsUser ? AnyView(View1().navigationBarHidden(true)) : AnyView(View2().navigationBarHidden(true)) , tag: 1, selection: $goToNextScreen){
EmptyView()
}
}
}
}
}
Only this part of code will print 4 times the log: INFO: [REALM] Realm opened: /var/mobile/
This part just checks the login and goes to a view.
How is this possible?
Is it because the the login block? Should I make the login and getUserProfile calls in a different way?
The repo.login method:
suspend fun login(email: String, password: String): User {
return appService.login(Credentials.emailPassword(email, password))
}
The repo.getUserProfile() that will return the type of the user:
fun getUserProfile(userId: String): CommonFlow<UserInfo?> {
val user = realm.query<UserInfo>("_id = $0", userId).asFlow().map {
it.list.firstOrNull()
}.asCommonFlow()
return user
}
enter code here
I ve uploaded a reproduction scenario: tmpfiles.org/1031395/mongo-conference-master3.zip
It is the same as the repository from mongodb github.com/mongodb-developer/mongo-conference, just with the getUserProfile changed with return CommonFlow
There is a very simple explanation why realm opens several time: Realm.open
call is inside RealmRepo
instance and you are creating multiple RealmRepo
instances.
For instance SwiftUI recreates RealmRepo
with every state change. Also other views e.g. HomeView
creates its own RealmRepo
and so on.
There are a lot of different ways to fix it. Here is some of them:
RealmRepo
objectJust change class RealmRepo
to object RealmRepo
. That will make your RealmRepo
a singleton in your iOS code you can access it like this:
var repo = RealmRepo.shared
EnvironmentObject
There is a simple Dependency Injection mechanism in SwiftUI that can help you use single instance of RealmRepo.
In your iOSApp.swift
:
import SwiftUI
@main
struct iOSApp: App {
@StateObject var repo = RealmRepo()
var body: some Scene {
WindowGroup {
LoginScreenView()
.environmentObject(repo)
}
}
}
and in all child views Login
, HomeView
, etc. use:
@EnvironmentObject var repo: RealmRepo
and do not create new instances.
More about @EnvironmentObject
you can read here
You can use some DI frameworks, e.g. Koin and specify RealmRepo
as singleton where when inject in iOS app.
RealmRepo
You can change how realm
field initialized in RealmRepo
. For example you can extract logic that opens Realm in a singleton:
class RealmRepo {
// ...
private val appService by lazy {
RealmHolder.appService
}
private val realm by lazy {
RealmHolder.realm
}
// ...
}
object RealmHolder {
private val schemaClass = setOf(UserInfo::class, SessionInfo::class, ConferenceInfo::class)
val appService by lazy {
val appConfiguration =
AppConfiguration.Builder(appId = "rconfernce-vkrny").log(LogLevel.ALL).build()
App.create(appConfiguration)
}
val realm by lazy {
val user = appService.currentUser!!
val config =
SyncConfiguration.Builder(user, schemaClass).name("conferenceInfo").schemaVersion(1)
.initialSubscriptions { realm ->
add(realm.query<UserInfo>(), name = "user info", updateExisting = true)
add(realm.query<SessionInfo>(), name = "session info", updateExisting = true)
add(
realm.query<ConferenceInfo>(),
name = "conference info",
updateExisting = true
)
}.waitForInitialRemoteData().build()
Realm.open(config)
}
}