1
votes

I get this error -

System.err: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}

Here is my fragment code -

@AndroidEntryPoint
class LoginView : Fragment(R.layout.login_view) {

    private val viewModel by viewModels<LoginViewModel>()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        view.login_button.setOnClickListener {
            val email = view.email_input_layout.text.toString()
            val password = view.password_input_layout.text.toString()
        
            viewModel.loginUser(email, password).observe(viewLifecycleOwner, {
                // ….
            })
        }
    }
}

Here is my ViewModel code -

fun loginUser(email: String, password: String)= repo.loginUser(Action.LoginUser(email, password)).map {
    when (it) {
       // …                    
     }
 }.asLiveData(Dispatchers.Default + viewModelScope.coroutineContext)

Here is the repo code -

fun loginUser(action: Action) = flow {
  when (action) {
     is Action.LoginUser -> {
            emit(Result.Loading)
            val result = remoteSource.loginUser(LoginModel(action.email, action.password))
            emit(result)
     }
  }
}

Here is the Remote Data Source Code -

override suspend fun loginUser(loginModel: LoginModel): Result {
    try {
         Log.e(TAG, “Going to do api call" + Thread.currentThread().name)
         val result = withContext(Dispatchers.IO) {
                  Log.e(TAG, “Doing api call" + Thread.currentThread().name)
                  apiInterface.loginUser(loginModel)
         }
         if (result.isSuccessful) {
            Log.e(TAG, “Api call success")
            return Result.Success(result.body())
         } else {
            Log.e(TAG, “Api call error”)
            return Result.Error(result.code())
         }
     } catch (e: Exception) {
       // Here I catch the JobCancellationException
        e.printStackTrace()
     }
     return Result.Error(UNKNOWN_ERROR)
}

And finally here is the Api Interface -

 @POST("login")
 suspend fun loginUser(@Body loginModel: LoginModel): Response<LoginResponseModel>

I have mocked the API response to be delivered in 20 seconds.

Here is the problem -

I hit the login button and the login call is happening. Now I see the logs -

Going to do api call main

Doing api call DefaultDispatcher-worker-1

As you see I am on the main thread until a call is requested and thread switching happens when I make the call.

Now I rotate the phone in the middle of the API call and my view is created new which is expected and I don't see any log and within few seconds I get the JobCancellationException which I catch in the RemoteDataSource. Could anyone guide me what exactly is the mistake here

1
are you extending Android ViewModel class or you have a custom view model?Nicola Gallazzi
My VM looks like this - class LoginViewModel @ViewModelInject constructor(private val repo: LoginRepository) : ViewModel() { ... } So I extend androidx.lifecycle.ViewModelMa2340
How is your viewModel created, is it done by @Inject lateinit var vm: MyViewModel?EpicPandaForce
@EpicPandaForce It is created in fragment as private val viewModel by viewModels<LoginViewModel>() where viewModels is androidx.fragment.app.viewModels.Ma2340
@EpicPandaForce I have updated my question with the fragment code as wellMa2340

1 Answers

0
votes

Ok I fixed the problem -

Here is my ViewModel -

var loginViewState: LiveData<LoginViewState> = MutableLiveData()

fun loginUser(email: String, password: String) {
         loginViewState = repo.loginUser(Action.LoginUser(email, password)).map {
                    // Do the mapping
                }.asLiveData(Dispatchers.Default + viewModelScope.coroutineContext)
}

And my Fragment looks like this -

@AndroidEntryPoint
class LoginView : Fragment(R.layout.login_view) {

    private val viewModel by viewModels<LoginViewModel>()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        view.login_button.setOnClickListener {
            val email = view.email_input_layout.text.toString()
            val password = view.password_input_layout.text.toString()
            viewModel.loginUser(email, password)  
            viewModel.loginViewState.observe(viewLifecycleOwner, {
               // …
            })      
        }

        viewModel.loginViewState.observe(viewLifecycleOwner, {
               // …
        })
    }
}

So the solution was to use a variable and observe the changes in it.