I've switched my code to View Binding but now I've got a problem with updating UI in a thread. The code worked OK in synthetic syntax. I'm getting error:
java.lang.NullPointerException
at HomeFragment.getBind(HomeFragment.kt:25)
at HomeFragment.updateHomeUI$lambda-6(HomeFragment.kt:190)
at HomeFragment.$r8$lambda$7K03ZbIZrY_5ngvcMBPsw15TPbw(Unknown Source:0)
at HomeFragment$$ExternalSyntheticLambda10.run(Unknown Source:2)
at java.lang.Thread.run(Thread.java:919)
My code:
class HomeFragment : Fragment(R.layout.fragment_home) {
private var _binding: FragmentHomeBinding? = null
private val bind get() = _binding!! // <-- line 25
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return bind.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
updateHomeUI()
}
private fun updateHomeUI() {
Thread {
while (bind.tvName != null) { // Stop the loop after changing the fragment
...
// Lots of UI update like this:
if (activity != null) (activity as MainActivity).runOnUiThread { bind.tvName?.text = str }
...
Thread.sleep(1000)
}
}.start()
}
The error shows up when I switch to a different fragment. If I am right, the thread is running after onDestroyView () and the binding goes empty. I came up with idea of pausing onDestroyView until Thread is finished but I think It's not best kind of solution because it may stop whole application.
override fun onDestroyView() {
super.onDestroyView()
threadStop = true
while (threadRunning) {
Thread.sleep(1)
}
_binding = null
}
private fun updateHomeUI() {
Thread {
threadRunning = true
threadStop = false
while (!threadStop) {
...
}
threadRunning = false
}
}
How to properly avoid this problem?
Best regards!
You may find this easier if you ditch the !!
getter and just use nullable types with local non-null variables as-needed. If you have a lot of places to use the binding inside the loop you could grab a non-null value of it at the start of the loop.
private fun updateHomeUI() {
Thread {
// this could also be while(true) since the calls just
// inside will break out when it goes null
while (_binding != null) { // Stop the loop after onDestroyView sets this to null
val binding = _binding ?: break
val a = activity as? MainActivity ?: break
// Lots of UI update like this:
a.runOnUiThread { binding.tvName.text = str }
...
Thread.sleep(1000)
}
}.start()
}
I'm not sure how your existing bind.tvName != null
loop condition would ever return false without first failing the !!
check. The views in the binding class are non-null by default (unless they don't exist in all layout permutations), so that would either be true, or would fail in the bind
getter when _binding
is null.