swiftrealmkotlin-multiplatformrealm-mobile-platformrealm-migration

Realm opens 4 times on Login


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


Solution

  • 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:

    Make your RealmRepo object

    Just 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
    

    Use 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

    Use some DI frameworks

    You can use some DI frameworks, e.g. Koin and specify RealmRepo as singleton where when inject in iOS app.

    Refactor 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)
        }
    }