https://kotlinlang.org logo
Title
d

Daniel

03/09/2023, 6:18 PM
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:
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:
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

Landry Norris

03/09/2023, 6:23 PM
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

Daniel

03/09/2023, 6:34 PM
The implementation of App.create its on the third method, after the getUserProfile()
l

Landry Norris

03/09/2023, 6:35 PM
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

Daniel

03/09/2023, 6:36 PM
You mean this?
App.create(appConfiguration)
l

Landry Norris

03/09/2023, 6:38 PM
Yes. What is App? Is that your class?
d

Daniel

03/09/2023, 6:38 PM
No, its from realm library:
l

Landry Norris

03/09/2023, 6:40 PM
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

Daniel

03/09/2023, 6:47 PM
items its just called on the getUserProfile
what do you mean same or different for both calls?
l

Landry Norris

03/09/2023, 6:49 PM
I misread your initial issue. I thought that it was returning the user twice.
d

Daniel

03/09/2023, 6:49 PM
No, it is opening the realm twice, not the user
l

Landry Norris

03/09/2023, 6:50 PM
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

Daniel

03/09/2023, 6:52 PM
No.. I do not, I just call RealRepo() once 😞
l

Landry Norris

03/09/2023, 6:54 PM
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

Daniel

03/09/2023, 6:57 PM
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

Landry Norris

03/09/2023, 7:00 PM
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

Daniel

03/09/2023, 7:04 PM
It does, it adds a new value to userInfo
l

Landry Norris

03/09/2023, 7:05 PM
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

Daniel

03/09/2023, 7:07 PM
So everytime the
@State UserInfo
changes, the view will run again? With all the calls?
l

Landry Norris

03/09/2023, 7:08 PM
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

Daniel

03/09/2023, 7:11 PM
Yeah you seem to be right
how I can avoid this?
l

Landry Norris

03/09/2023, 7:12 PM
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

Daniel

03/09/2023, 7:16 PM
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

Landry Norris

03/09/2023, 7:17 PM
If it directly affects rendering, it’s state. In this case, myUser is state.
d

Daniel

03/09/2023, 8:56 PM
View Model Login did not fix the issue the issue unfortunately, same number of realms are opened
Any other advice?
l

Landry Norris

03/09/2023, 8:57 PM
Are you creating the VM in the view?
Is this code available on a GitHub? I can look in a bit if so.