简体   繁体   中英

Android Kotlin: Setting fragment TextView background on start from ViewModel

I am confused by a certain inconsistency in my code, where only part of the data is loading. I am trying to set up a grid of TextViews in my fragment, which read from a list variable called board on the ViewModel for that fragment. The TextView text is set as board[n].text from the view model, where n is its index in the list, and this loads just fine. I am also trying to set the TextView background to one of three background resources, which are saved as an int board[n].marking on the view model.

This does not work. It seems that it is trying to load the background for each TextView before board has been fully initialized in the view model, but it does not seem to try to do the same for the TextView text. Here are the relevant parts of my code. First, the XML layout:

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".screens.game.GameFragment">

    <data>
        <import type="android.view.View"/>
        <variable
            name="gameViewModel"
            type="com.example.mygametitle.screens.game.GameViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

(...)

        <TextView
            android:id="@+id/field13"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginTop="@dimen/board_vertical_margin"
            android:background="@{gameViewModel.board[2].marking}"
            android:onClick="@{ () -> gameViewModel.openGameDialog(2, field13)}"
            android:text="@{gameViewModel.board[2].text}"
(...)

There are 25 fields like that. All of the text loads properly, but none of the background images load. If instead I hardcode the background I want, as such, it loads properly: android:background="@drawable/board_fieldbackground_checked" . This won't work for me though, as I need to read what each entry's background is upon startup--they don't all start checked.

On the view model, board is made by reading a set of 25 entries from a Room database, each including (among other info) a text string and a marking int. These all update properly--if I use a debug function to print out the contents of my board, they all have the proper text and marking upon closing and reopening the fragment. When the fragment opens, all the text is correct, but the backgrounds are not. Any ideas on why my backgrounds aren't loading the same way the text is?

Here's some of the relevant viewmodel code:

class GameViewModel(
    val database: BoardDatabaseDao,
    application: Application,
    val boardTitle: String) : AndroidViewModel(application)  {

    val BG_UNMARKED = R.drawable.board_fieldbackground_bordered
    val BG_CHECKED = R.drawable.board_fieldbackground_checked
    val BG_MISSED = R.drawable.board_fieldbackground_missed

    private val thisBoardEntries = MutableLiveData<List<BoardField>?>()

    private val _board = MutableLiveData<List<BoardField>>()
    val board: LiveData<List<BoardField>>
        get() = _board

    private suspend fun getEntries() : List<BoardField>? {
        Log.i("GameViewModel", "Running database.getFromParent(boardTitle), function getEntries().")
        val entries = database.getFromParent(boardTitle)
        return entries
    }

    init {
        viewModelScope.launch {
            Log.i("GameViewModel", "Start viewModelScope.launch on init block.")
            thisBoardEntries.value = getEntries()

            if (thisBoardEntries.value?.isEmpty()!!) {
                Log.i(
                    "GameViewModel",
                    "allEntries.value is EMPTY, seemingly: ${thisBoardEntries.value}, should be empty"
                )
            } else {
                Log.i(
                    "GameViewModel",
                    "allEntries.value is NOT empty, seemingly: ${thisBoardEntries.value}, should be size 25"
                )
                _board.value = thisBoardEntries.value
            }
        }
    }



    fun markFieldMissed(index: Int, view: TextView) {
        Log.i("GameViewModel", "My Textview looks like this: $view")

        _board.value!![index].marking = BG_MISSED
        view.setBackgroundResource(BG_MISSED)
        Log.i("GameViewModel", "Set background to $BG_MISSED")
        val color = getColor(getApplication(), R.color.white_text_color)
        view.setTextColor(color)

        viewModelScope.launch {
            val markedField = getEntryAtIndex(boardTitle, convertIndexToLocation(index))
            Log.i("GameViewModel", "I think markedField is $markedField")
            if (markedField != null) {
                markedField.marking = BG_MISSED
                update(markedField)
                Log.i("GameViewModel", "Updated field with $BG_MISSED marking on DB: $markedField")
            }
        }
    }

    fun markFieldChecked(index: Int, view: TextView) {
        _board.value!![index].marking = BG_CHECKED
        view.setBackgroundResource(BG_CHECKED)
        Log.i("GameViewModel", "Set background to $BG_CHECKED")
        val color = getColor(getApplication(), R.color.white_text_color)
        view.setTextColor(color)

        viewModelScope.launch {
            val markedField = getEntryAtIndex(boardTitle, convertIndexToLocation(index))
            Log.i("GameViewModel", "I think markedField is $markedField")
            if (markedField != null) {
                markedField.marking = BG_CHECKED
                update(markedField)
                Log.i("GameViewModel", "Updated field with $BG_CHECKED marking on DB: $markedField")
            }
        }
    }

    fun debugPrintEntries() {
        Log.i("GameViewModel", "DebugPrintEntries function: ${_board.value}")
    }


(2020-11-05) Edit 1: Part of the issue was indeed a resource not being read as such. I made the following additions/changes in my layout XML, which gets me a bit further:

    <data>
        <import type="androidx.core.content.ContextCompat"/>
(...)
    </data>

        <TextView
(...)
            android:background="@{ContextCompat.getDrawable(context, gameViewModel.BG_CHECKED)}"
(...)

With a hardcoded resource for BG_CHECKED as my background image, everything loads and displays nicely. The problem is once again that the background is not read from board[4].marking (which contains BG_CHECKED as its value), although the text has no problem being read from board[4].text

The following replacement in the layout XML does not work, causing an exception: Caused by: android.content.res.Resources$NotFoundException: Resource ID #0x0 with the line

android:background="@{ContextCompat.getDrawable(context, gameViewModel.board[4].marking)}"

I haven't used data binding, but I think it might be because you're just providing an Int as the background, which happens to represent a resource ID - but the data binding doesn't know that, so it doesn't know it needs to resolve it to a drawable value in resources? When you set it manually you're explicitly telling it to do that by using the @drawable syntax

Here's a blog where someone runs into something similar (well that situation anyway, but with colours) - their second solution is to add a ContextCompat import to the data block, and then use that to do a ContextCompat.getColor lookup in the data binding expression. Maybe you could do something similar to get the drawable you need

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM