How can I resolve this NPE error when fetching data in an API?

Asked

Viewed 32 times

0

I’m looking to do a search on the Github API using Kotlin Coroutines in a different thread than the main one, but by receiving the values, it’s generating this error:

021-01-27 19:50:22.961 17831-17831/com.posart.githubinfo E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.posart.githubinfo, PID: 17831
    java.lang.NullPointerException: Attempt to invoke virtual method 'int com.posart.githubinfo.network.UserNetwork.getFollowers()' on a null object reference
        at com.posart.githubinfo.views.details.DetailsFragment$onCreateView$1.onChanged(DetailsFragment.kt:47)
        at com.posart.githubinfo.views.details.DetailsFragment$onCreateView$1.onChanged(DetailsFragment.kt:17)
        at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
        at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
        at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
        at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
        at androidx.lifecycle.LiveData$1.run(LiveData.java:91)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:224)
        at android.app.ActivityThread.main(ActivityThread.java:7562)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

I’m not sure how to use the corroding, but I think the problem isn’t exactly in their use. I think when the fragment gets the variable data, the API search hasn’t been done yet. I’ll put here the files I think may be associated with the error:

Repository

class UserRepository {
    fun getUser(username: String): LiveData<UserNetwork> {
        val userResponse = MutableLiveData<UserNetwork>()

        GitHubApi().getUser(username).enqueue(object : Callback<UserNetwork> {
            override fun onFailure(call: Call<UserNetwork>, t: Throwable) {
                userResponse.value = null
            }

            override fun onResponse(call: Call<UserNetwork>, response: Response<UserNetwork>) {
                if (response.isSuccessful) {
                    userResponse.value = response.body()
                } else {
                    userResponse.value = null
                }
            }

        })

        return userResponse
    }

    fun getUserRepos(username: String): LiveData<List<RepoNetwork>> {
        val reposResponse = MutableLiveData<List<RepoNetwork>>()
        
        GitHubApi().getReposUser(username).enqueue(object : Callback<List<RepoNetwork>> {
            override fun onFailure(call: Call<List<RepoNetwork>>, t: Throwable) {
                reposResponse.value = null
            }

            override fun onResponse(
                call: Call<List<RepoNetwork>>,
                response: Response<List<RepoNetwork>>
            ) {
                if (response.isSuccessful) {
                    reposResponse.value = response.body()
                } else {
                    reposResponse.value = null
                }
            }
        })
        
        return reposResponse
    }
}

Viewmodel

class DetailsViewModel(private val username: String) : ViewModel() {

    private val _user = MutableLiveData<UserNetwork>()
    val user: LiveData<UserNetwork>
        get() = _user

    private val _reposUser = MutableLiveData<List<RepoNetwork>>()
    val reposUser: LiveData<List<RepoNetwork>>
        get() = _reposUser

    init {
        viewModelScope.launch {
            withContext(Dispatchers.IO) {
                _user.postValue(UserRepository().getUser(username).value)
                _reposUser.postValue(UserRepository().getUserRepos(username).value)
            }
        }
    }

    class Factory(private val username: String) : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            if (modelClass.isAssignableFrom(DetailsViewModel::class.java)) {
                @Suppress("UNCHECKED_CAST")
                return DetailsViewModel(username) as T
            }
            throw IllegalArgumentException("Unable to construct viewmodel")
        }

    }

}

Fragment

class DetailsFragment : Fragment() {

    private lateinit var viewModel: DetailsViewModel

    private fun adapterOnClick(repository: RepoNetwork) {
        val intent = Intent(Intent.ACTION_VIEW)
        intent.data = Uri.parse(repository.html_url)
        startActivity(intent)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding = DetailsFragmentBinding.inflate(inflater)

        val arguments =
            DetailsFragmentArgs.fromBundle(
                requireArguments()
            )

        val factory = DetailsViewModel.Factory(arguments.username)
        viewModel = ViewModelProvider(this, factory).get(DetailsViewModel::class.java)

        binding.viewModel = viewModel
        binding.lifecycleOwner = this

        viewModel.user.observe(viewLifecycleOwner, Observer {
            binding.followersAndFollowing.text = getString(
                R.string.followers_and_following,
                it.followers, it.following
            )
        })

        viewModel.reposUser.observe(viewLifecycleOwner, Observer {
            binding.recyclerViewList.adapter = ReposAdapter(it) {
                repository -> adapterOnClick(repository)
            }
        })

        return binding.root
    }
}
  • Apparently the code works, it would need to run the project on A.S to know the exact point that triggers the exception. If you were on Github it would be a lot easier to help.

  • 1

    I already managed to fix the bug. Thanks for your help

1 answer

1

I don’t know Kotlin, and I don’t understand all the code. And I’m not sure about the concept of co-routines, I have with me that are code that pass control of the execution from one thread to another manually through an API.

It seems to be happening what you said, the variable being null because it is being accessed before it has been updated by callback, which normally like all callback will only be called at the end of a long operation (for example network) asynchronously in relation to the time of the call.

Solving the problem therefore should derive from this notion, for example if you are in a given code or view snippet calling the asynchronous operation and trying to access the result right in the sequence (ie synchronously) will not be able.

You must access the variable at a point where it has become available, either within the callback, or by "posting" the code that uses this variable in the interested thread (usually the main thread, which takes care of the display of the graphic components), or in the case of this form of co-routine that I described passing control to the thread that makes use of the data, or still using some feature that facilitates this asynchronous treatment, for example a possibility is to search if there is something in Kotlin similar to the Asynctasks that are used in Java on Android. In fact this co-routine/callbacks code you showed seems to want you to put the code that makes use of the returned API data into the callback itself, which in this case is onResponse() or similar.

Actually, there must be a way to solve it in Kotlin that I’m not sure about because I don’t know the language. Even so, the answer should certainly go through the understanding of how the life cycle of Android components works, such as Fragments, and its relationship with the main thread. Understanding that you kill the question.

P.S.: I did not understand what would be a Userrepository that accesses network information (the name being only User) and a repository, so far as I know, is a mechanism to abstract access to data, for example from a database, in a generally synchronous way, without involving callbacks.

You also have to understand the question of Binding which is being done, which is a feature of Android with MVVM that I didn’t get to track how it works.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.