Обновление представлений асинхронно - PullRequest
6 голосов
/ 05 апреля 2019

Я пытаюсь заполнить recyclerview данными из Интернета, которые я хочу получить асинхронно.

У меня есть функция loadData(), которая называется onCreateView(), которая сначала делает видимым индикатор загрузки, затем вызывает функцию приостановки загрузки данных и затем пытается уведомить адаптер представления об обновлении.

Но в этот момент я получаю следующее исключение:

android.view.ViewRootImpl $ CalledFromWrongThreadException: только исходный поток, создавший иерархию представлений, может касаться его представлений.

, что удивило меня, так как мое понимание состояло в том, что только моя функция get_top_books() была вызвана в другом потоке, и ранее, когда я показывал индикатор загрузки, я, очевидно, был в правом потоке.

Так почему возникает это исключение во время выполнения?

Мой код:

class DiscoverFragment: Fragment() {

    lateinit var loadingIndicator: TextView
    lateinit var viewAdapter: ViewAdapter
    var books = Books(arrayOf<String>("no books"), arrayOf<String>("no books"), arrayOf<String>("no books"))

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val viewFrame = layoutInflater?.inflate(R.layout.fragment_discover, container, false)

        val viewManager = GridLayoutManager(viewFrame!!.context, 2)
        viewAdapter = ViewAdapter(books)
        loadingIndicator = viewFrame.findViewById<TextView>(R.id.loading_indicator)

        val pxSpacing = (viewFrame.context.resources.displayMetrics.density * 8f + .5f).toInt()

        val recyclerView = viewFrame.findViewById<RecyclerView>(R.id.recycler).apply {
            setHasFixedSize(true)
            layoutManager = viewManager
            adapter = viewAdapter
            addItemDecoration(RecyclerViewDecorationSpacer(pxSpacing, 2))
        }

        loadData()

        return viewFrame
    }

    fun loadData() = CoroutineScope(Dispatchers.Default).launch {
        loadingIndicator.visibility = View.VISIBLE
        val task = async(Dispatchers.IO) {
            get_top_books()
        }
        books = task.await()
        viewAdapter.notifyDataSetChanged()
        loadingIndicator.visibility = View.INVISIBLE
    }
}

Ответы [ 4 ]

4 голосов
/ 05 апреля 2019

После вызова books = task.await() вы находитесь вне потока пользовательского интерфейса.Это потому, что вы используете CoroutineScope(Dispatchers.Default).Измените его на Dispatchers.Main:

fun loadData() = CoroutineScope(Dispatchers.Main).launch {
        loadingIndicator.visibility = View.VISIBLE
        val task = async(Dispatchers.IO) {
            get_top_books()
        }
        books = task.await()
        viewAdapter.notifyDataSetChanged()
        loadingIndicator.visibility = View.INVISIBLE
    }
1 голос
/ 05 апреля 2019

После вызова books = task.await() вы находитесь вне потока пользовательского интерфейса.Вы должны запустить весь связанный с пользовательским интерфейсом код в основном потоке.Для этого вы можете использовать Dispatchers.Main.

CoroutineScope(Dispatchers.Main).launch {
    viewAdapter.notifyDataSetChanged()
    loadingIndicator.visibility = View.INVISIBLE
}

Или использовать Handler

Handler(Looper.getMainLooper()).post { 
    viewAdapter.notifyDataSetChanged()
    loadingIndicator.visibility = View.INVISIBLE
}

Или вы можете использовать Activty экземпляр для вызова runOnUiThread метода.

activity!!.runOnUiThread {
    viewAdapter.notifyDataSetChanged()
    loadingIndicator.visibility = View.INVISIBLE
}
0 голосов
/ 05 апреля 2019

Изменение Dispatchers.Default на Dispatchers.Main и обновление моей версии kotlinx-coroutines-android до 1.1.1 сделали свое дело.

Изменение

val task = async(Dispatchers.IO) {
    get_top_books()
}
books = task.await()

до

books = withContext(Dispatchers.IO) {
    get_top_books()
}

также немного элегантнее. Спасибо всем, кто откликнулся, особенно @DominicFischer, у которого была идея проверить мои зависимости.

0 голосов
/ 05 апреля 2019

Операторы, связанные с пользовательским интерфейсом, должны выполняться в потоке пользовательского интерфейса, я не могу сказать только по связанному коду, но, возможно, вы меняете пользовательский интерфейс в этой функции get_top_books().

Просто поместите соответствующий код пользовательского интерфейсав потоке пользовательского интерфейса, как это

runOnUiThread(
    object : Runnable {
        override fun run() {
            Log.i(TAG, "runOnUiThread")
        }
    }
)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...