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

State Update Issue in ios Swiftui #88

Open
calwato opened this issue Jan 15, 2025 · 2 comments
Open

State Update Issue in ios Swiftui #88

calwato opened this issue Jan 15, 2025 · 2 comments

Comments

@calwato
Copy link

calwato commented Jan 15, 2025

I am encountering an issue where the UI in my SwiftUI application does not update correctly after the ExterUserViewModel's state is modified.

Below is the relevant code for my ExterUserViewModel:

open class ExterUserViewModel() : ViewModel() {

// StateFlow to hold the current state
private val _exterUserState: MutableStateFlow<ExterUserState> =
    MutableStateFlow(viewModelScope, ExterUserState())

@NativeCoroutinesState
val exterUserState: StateFlow<ExterUserState> =
    _exterUserState.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ExterUserState())

fun searchUser(phoneNumber: String) {
    viewModelScope.launch {
        try {
            // Update state to indicate loading
            _exterUserState.value = _exterUserState.value.copy(
                isLoading = true,
                error = null // Clear any previous errors
            )

            if (phoneNumber.isBlank()) {
                throw IllegalArgumentException("Phone number is required")
            }

            // Simulate a user search operation
            println("Searching user with phone number: $phoneNumber")
            delay(1000) // Simulating API call or computation delay

            // Update state with success result
            _exterUserState.value = _exterUserState.value.copy(
                isLoading = false,
            )

        } catch (e: IllegalArgumentException) {
            // Handle validation errors
            _exterUserState.value = _exterUserState.value.copy(
                isLoading = false,
                error = e.message
            )
        } catch (e: Exception) {
            // Handle other errors
            _exterUserState.value = _exterUserState.value.copy(
                isLoading = false,
                error = "An unexpected error occurred"
            )
        }
    }
}

}
in swiftui
@StateViewModel var viewModel = ExterUserViewModel()

var body: some View {
VStack {
if viewModel.exterUserState.isLoading {
ProgressView("Loading...")
} else if let error = viewModel.exterUserState.error {
Text("Error: (error)")
} else {
Text("User search completed")
}

    Button("Search User") {
        viewModel.searchUser(phoneNumber: "") // Example: Blank input to trigger validation
    }
}

}

Problem
When I press the "Search User" button with an empty phone number, I correctly see an error message displayed in the UI (e.g., "Phone number is required").
On subsequent presses of the same button, the ViewModel's state is updated correctly, as verified through logs, but the SwiftUI UI does not reflect the updated state.

Expected Behavior
The SwiftUI UI should update to reflect the latest changes in the ViewModel's state whenever the state is modified.

@rickclephas
Copy link
Owner

On subsequent presses of the same button, the ViewModel's state is updated correctly, as verified through logs, but the SwiftUI UI does not reflect the updated state.

Hi! I am assume you mean that SwiftUI doesn't show the loading state, correct?

In that case this behaviour is expected.
viewModelScope.launch will launch a new async job on the Main dispatcher / thread.
Everything inside the launch up until the delay will run uninterrupted.

Changing the state value only notifies SwiftUI that the state has changed.
But only once the main thread is free SwiftUI will/can rerender the view.

So adding a delay before the phoneNumber check would allow SwiftUI to update the view.
Similarly launching the job on another dispatcher (such as Default) would also allow SwiftUI to update the view.

However there is no guarantee that SwiftUI will update the view faster than you are updating the state (unless you are using a significant delay).

@calwato
Copy link
Author

calwato commented Jan 15, 2025

These solutions work well! Before calling viewModel.searchUser(phoneNumber: ""), I now reset the state using this method:

open fun resetState() {
_exterUserState.value = ExterUserState()
}

This clears the previous state and ensures the UI updates correctly. I also plan to try the delay idea you suggested, like this:

delay(100)

I’ll experiment with this approach to see if it helps SwiftUI reflect the loading state more reliably. Thanks for the suggestion!

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

2 participants