Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS action handling not always working with Firebase calls #267

Open
kostapostolakis opened this issue Mar 11, 2024 · 6 comments
Open

iOS action handling not always working with Firebase calls #267

kostapostolakis opened this issue Mar 11, 2024 · 6 comments

Comments

@kostapostolakis
Copy link

kostapostolakis commented Mar 11, 2024

I have the NewGameViewModel in commonMain Shared code of KMM.

interface NewGameIntents {
    fun updateUserName(userName: String)
    fun startNewGameButtonTapped()
    fun newGameCreated(newGameID: String)
    fun newGameCreationFailed()
}

sealed interface NewGameActions {
    data class CreateNewGameAction(val userName: String): NewGameActions
    data class GoToMainScreenAction(val gameID: String, val userName: String) : NewGameActions
}

class NewGameViewModel: BaseViewModel(), NewGameIntents {
    private val _actions = Channel<NewGameActions>()
    val actions: CFlow<NewGameActions> get() = _actions.receiveAsFlow().cFlow()

    private val _userName: CMutableStateFlow<String> = MutableStateFlow("").cMutableStateFlow()
    val userName: CStateFlow<String> = _userName.cStateFlow()

    init {
        val previousName = SettingsHelper().lastGameName ?: SettingsHelper().userName
        previousName?.let {
            updateUserName(it)
        }
    }

    override fun updateUserName(userName: String) {
        _userName.value = userName
    }

    override fun startNewGameButtonTapped() {
        updateUserName(_userName.value.trim())
        createNewGame()
    }

    override fun newGameCreated(newGameID: String) {
        showLoading(false)
        viewModelScope.launch {
            _actions.send(NewGameActions.GoToMainScreenAction(newGameID, _userName.value))
        }
    }

    override fun newGameCreationFailed() {
        showLoading(false)
        showErrorAlert()
    }

    private fun createNewGame() {
        showLoading(true)
        viewModelScope.launch {
            _actions.send(NewGameActions.CreateNewGameAction(_userName.value))
        }
    }
}

I have also the NewGameScreen in iOS:

struct NewGameScreen: View {
    
    @ObservedObject var newGameViewModel: NewGameViewModel = NewGameViewModel()
    
    var body: some View {        
        ScrollView {
            VStack(spacing: 0) {
                CustomTextField(
                    value: newGameViewModel.state(\.userName),
                    label: getKmmString(stringResource: \.your_name),
                    onTextChange: { text in
                        newGameViewModel.updateUserName(userName: text)
                    }
                )
                
                Spacer().frame(height: 32)
                CustomButton(
                    text: getKmmString(stringResource: \.start_game),
                    onClick: {
                        newGameViewModel.startNewGameButtonTapped()
                    }
                )
            }
            .padding(16)
        }
        .navigationBarBackButtonHidden(true)
        .onReceive(createPublisher(newGameViewModel.actions)) { action in
            handleNewGameAction(action: action)
        }
    }
    
    private func handleNewGameAction(action: NewGameActions) {
        switch action {
        case let action as NewGameActionsCreateNewGameAction: do {
            let newGameMap = getMapFromFunction(name: action.userName) // Create map here
            
            var documentReference: DocumentReference? = nil
            documentReference = Firestore.firestore()
                .collection(Game.companion.COLLECTION_GAMES)
                .addDocument(data: newGameMap) { error in
                    if let error {
                        newGameViewModel.doNewGameCreationFailed()
                    } else if let documentId = documentReference?.documentID, !documentId.isEmpty {
                        newGameViewModel.doNewGameCreated(newGameID: documentId)
                    } else {
                        newGameViewModel.doNewGameCreationFailed()
                    }
                }
        }
            
        case let action as NewGameActionsGoToMainScreenAction: do {
            closeScreen()
            PalermoHelper().goToMainGameScreen(gameID: action.gameID, name: action.userName)
        }
            
        default: return
        }
    }
}

When I tap the button, startNewGameButtonTapped() function of viewModel is called and then the action CreateNewGameAction should be handled by iOS.

Sometimes it works and sometimes not. Maybe it is a lifecycle issue, but I am not sure and I do not know how to solve it. Do you have any ideas?

@kostapostolakis kostapostolakis changed the title iOS Action Handling not working iOS action handling not working sometimes (maybe lifecycle issue) Mar 11, 2024
@kostapostolakis kostapostolakis changed the title iOS action handling not working sometimes (maybe lifecycle issue) iOS action handling not working more times (maybe lifecycle issue) Mar 11, 2024
@alobaili
Copy link

I'm also having the same issue, for some reason on some screens, onReceive with createPublisher does not correctly receive the emitted value and the block is not triggered even through the value is correctly emitted from the shared module.

@kostapostolakis
Copy link
Author

Do we have any solution on this?

@kostapostolakis kostapostolakis changed the title iOS action handling not working more times (maybe lifecycle issue) iOS action handling not working with Firebase calls Dec 3, 2024
@kostapostolakis kostapostolakis changed the title iOS action handling not working with Firebase calls iOS action handling not always working with Firebase calls Dec 3, 2024
@Alex009
Copy link
Member

Alex009 commented Dec 3, 2024

maybe publisher disposed before receive event, but cancellation of Flow subscription done in async way, so we have small period when Flow still active and receive event, but Publisher already cancelled and can't receive new events.

try to change code to:

    func cancel() {
            self.disposable.dispose()
    }

@kostapostolakis
Copy link
Author

It seems that is working. I test all the flows and I will inform you. Regarding Android, it has the same problem. I should call GlobalScope.launch instead of viewModelScope.launch to play correctly. Do you have any suggestions here?

@kostapostolakis
Copy link
Author

kostapostolakis commented Dec 3, 2024

iOS is not playing when I make a Firebase call and onSuccess I make another one directly. Any thoughts?

@kostapostolakis
Copy link
Author

kostapostolakis commented Dec 11, 2024

I have something like this in ViewModel:

viewModelScope.launch {
      _profileActions.send(ProfileActions.GetUserDetails)
      _profileActions.send(ProfileActions.GetUserStatistics)
}

In every action, I make a call in Firebase, but the first call is not made.

If I change viewModelScope -> GlobalScope, it plays correctly!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants