Realm opens twice at login. I have a kmm app. On ...
# ios
d
Realm opens twice at login. I have a kmm app. On Swift, when I do the login, somehow the Realm opens twice. My login view:
Copy code
struct LoginView: View {

    @State var userId : String = ""
    @State var password : String = ""
    @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().watch(block:  {items in
                                
                                self.myUser = items as! UserInfo
                                
                            })
                            
                        }
                    }
                    
                }

            }
        }
    }
}
I do the login, and if the login is ok, I get the profile of the user. My realm repo:
Copy code
suspend fun login(email: String, password: String): User {
        return appService.login(Credentials.emailPassword(email, password))
    }

fun getUserProfile(): CommonFlow<UserInfo?> {
        val userId = appService.currentUser!!.id

        val user = realm.query<UserInfo>("_id = $0", userId).asFlow().map {
            it.list.firstOrNull()
        }.asCommonFlow()

        return user
    }

 private val appService by lazy {
        val appConfiguration =
            AppConfiguration.Builder(appId = "xxxx").log(LogLevel.ALL).build()
        App.create(appConfiguration)
    }

 private val realm by lazy {
        val user = appService.currentUser!!

        val config =
            SyncConfiguration.Builder(user, schemaClass).name("xxxx").schemaVersion(1)
                .initialSubscriptions { realm ->
                    add(realm.query<UserInfo>(), name = "user info", updateExisting = true)

                }.waitForInitialRemoteData().build()
        Realm.open(config)
    }
When I do the login, will print 2 times the log:
INFO: [REALM] Realm opened: /var/mobile/
How is this possible? Is it because the the login block? Should I make the login and getUserProfile calls in a different way?
l
I’d first put a log when you call login to see if the button’s handler is getting called twice. I don’t see the implementation of App.create, so it’s hard to see what’s actually happening when you call appService.login. Is the user info the same each time?
If you start watching too early, you may be getting some default data, then another event when the real data is saved.
d
The implementation of App.create its on the third method, after the getUserProfile()
l
It looks like that’s where it’s called. What does App.create do? What’s the return type?
Is it from a library? Which one?
d
You mean this?
Copy code
App.create(appConfiguration)
l
Yes. What is App? Is that your class?
d
No, its from realm library:
l
I see. Can you print the value of items in the watch block? I’d want to check if the value is the same or different for both calls.
d
items its just called on the getUserProfile
what do you mean same or different for both calls?
l
I misread your initial issue. I thought that it was returning the user twice.
d
No, it is opening the realm twice, not the user
l
You are creating the RealmRepo in the LoginView (I’m assuming the realm by lazy is in RealmRepo, and not a companion object). Are you constructing it anywhere else? A lazy var is per-instance, so if you construct a new RealmRepo, it will open again when you first use the realm field.
d
No.. I do not, I just call RealRepo() once 😞
l
When myUser is updated, SwiftUI should detect that LoginView is invalidated, and reconstruct it (since myUser is marked as state). This will also construct a new repo. Make sure when using a declarative library to not construct items unrelated to UI in your view.
This should be similar to constructing repo in a composable function. Try hoisting the repo to some ViewModel or something outside of SwiftUI.
d
I am C#, C++ developer, this is Chinese to me.. Can you please explain in another way?
For example if I call another repo method, this will open another repo..
This opens 3 realms..
l
SwiftUI is what we call ‘Declarative’. The fields you have marked as @State tells SwiftUI to build a new LoginView when they change. The way you have it currently, every time
getUserProfile()
emits a new value, you update
self.myUser
, and SwiftUI sees that LoginView’s state has changed, and re-creates it. This is fine, since it’s expected that when you change the state, SwiftUI re-creates your LoginView so it can show any changes. In this case, however, you construct a new repo whenever LoginView gets constructed.
Does
insertFCMWhenLogin
modify the user profile at all, such that the watch would emit a new value?
d
It does, it adds a new value to userInfo
l
When it does that, the watch runs again, modifying self.myUser, which SwiftUI sees as a state change. Because the state changed, it re-constructs LoginView. You create repo in the LoginView, so it constructs a new RealmRepo.
Since it’s a new RealmRepo, it doesn’t have the old Realm cached, and it creates a new one.
d
So everytime the
@State UserInfo
changes, the view will run again? With all the calls?
l
The TL;DR is that when using any declarative framework, you shouldn’t create any objects in the View. Always hold that in some sort of ViewModel.
Whenever myUser changes, the LoginView and its body are re-created. This includes constructing a new RealmRepo.
I’d put a breakpoint on Realm.open and see where it gets created each time.
d
Yeah you seem to be right
how I can avoid this?
l
Create a ViewModel or equivalent and hold your repo in that. Pass it down to your view. Never create an object inside a view that isn’t related to rendering that view.
d
But in that way, if I have a object that I want to be updated real time, for example a list and I want to edit that list realm time. How my screen will be updated?
l
If it directly affects rendering, it’s state. In this case, myUser is state.
Are you creating the VM in the view?
Is this code available on a GitHub? I can look in a bit if so.