принудительная рекомпозиция (Android compose)

avatar
NotVeryCreative
8 августа 2021 в 17:12
757
2
1

код:

package com.example.saveandloadusername

import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.runtime.*

import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp

import com.example.saveandloadusername.ui.theme.SaveAndLoadUserNameTheme
import java.io.File
import java.io.IOException

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            SaveAndLoadUserNameTheme {
                Surface(color = MaterialTheme.colors.background) {
                    MainScreen(baseContext)
                }
            }
        }
    }
}

@Composable
fun MainScreen(context: Context) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    )
    {
        var name by remember { mutableStateOf("")}

        if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
            Text(text="Hello, give me your name :)")
        } else {
            Text(text="welcome back ${readNameFromInternalStorage(context)}")
        }

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        OutlinedTextField(
            value=name,
            onValueChange={ name = it },
            label={Text(text="Name")},
            singleLine = true,
            keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Words)
        )

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        Button(
            onClick = {
                name = name.replace(" ", "".replace("\n", ""))
                if(name == "") {
                    Toast.makeText(context, "name invalid", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(context, "name (${name}) saved :D", Toast.LENGTH_SHORT).show()
                    saveNameToInternalStorage(name, context)
                }
            },
        )
        {
            Text(text="save")
        }

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        Button(
            onClick = {
                if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
                    Toast.makeText(context, "you need to give me a name first ;)", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(
                        context,
                        "the name is: '${readNameFromInternalStorage(context)}'",
                        Toast.LENGTH_SHORT
                    ).show()
                }

            },
        )
        {
            Text(text="check")
        }

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        Button(onClick = { cleanNameData(context)}) {
            Text("Remove name")
        }

    }

}

private fun saveNameToInternalStorage(name: String, context: Context): Boolean {
    return try {
        context.applicationContext.openFileOutput("name.txt", MODE_PRIVATE).use { stream ->
            stream.flush()
            stream.write(name.toByteArray())
            Log.i("SAVE_STATE","name ($name) written as ${name.toByteArray()}")
        }
        return true
    } catch(e: IOException) {
        e.printStackTrace()
        false
    }
}

private fun readNameFromInternalStorage(context: Context): String {
    val file = File(context.filesDir, "name.txt")
    return if (file.exists()) {
        val contentOfFile = file.readBytes().decodeToString()
        Log.i("CONTENTTT", contentOfFile)
        contentOfFile
    } else {
        ""
    }
}

private fun checkIfNameIsEmpty(name: String): Boolean {
    return name.isEmpty()
}

private fun cleanNameData(context: Context) {
    context.applicationContext.openFileOutput("name.txt", MODE_PRIVATE).use { stream ->
        stream.flush()
        Log.i("cleanNameData", "Name Removed from memory")
        Toast.makeText(context, "Name removed", Toast.LENGTH_SHORT).show()
    }
}


Я создал небольшое приложение, чтобы познакомиться с компоновкой Android, и я наткнулся на проблему, которую не могу решить: текст (над текстовым полем) не обновляется после нажатия кнопок «удалить имя» или «сохранить», текст обновляется только тогда, когда что-то изменяется в текстовом поле, есть ли способ принудительно перекомпоновать этот текст вручную? Любая помощь приветствуется :)

Источник
Richard Onslow Roper
8 августа 2021 в 18:39
0

Вы имеете в виду сохранить и "проверить"?

NotVeryCreative
8 августа 2021 в 20:25
0

Я пытаюсь понять, как работает сохранение, загрузка, удаление из внутренней памяти: «сохранить» -> сохранить имя во внутреннюю память, «проверить» -> проверить, какое имя в данный момент сохранено, «удалить имя» -> очистить внутренняя память

Ответы (2)

avatar
Richard Onslow Roper
8 августа 2021 в 18:51
0

Во-первых, если все, что вам нужно сделать, это отобразить это имя там, то зачем утруждать себя сохранением его в хранилище, а затем чтением? Предложение: вы можете просто использовать параметр имени в качестве значения Text, а после модификации вы можете просто сохранить новое имя в хранилище и, если транзакция завершится успешно, обновить переменную. Это вызовет необходимую рекомпозицию.

Далее, причина, по которой текст не обновляется, заключается в том, что метод, который вы используете для получения имени из хранилища, возвращает примитивный тип, а не LiveData. Следовательно, вместо того, чтобы вручную запускать перекомпоновку, если вы реализуете метод самостоятельно, попробуйте сначала прочитать о LiveData. Это не должно быть сложно реализовать.

ОДНАКО (не рекомендуется), если все, что вам нужно, это метод ручного повторного запуска композиции, то вот он.

Как вы, возможно, знаете, Compose использует объекты MutableState для наблюдения за данными, с которыми вы, кажется, хорошо знакомы, поскольку уже используете их. Следовательно, все, что вам нужно сделать, это просто добавить «фиктивную» переменную типа MutableState, а затем изменить ее с помощью onClick соответствующих кнопок. Кроме того, вы должны дать Compose сообщение о том, что фиктивная переменная читается в Text, чтобы вызвать перекомпоновку.

@Composable
fun MainScreen(context: Context) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    )
    {
        var name by remember { mutableStateOf("")}
        var dummy by mutableStateOf(false) // don't even need to remember, long as it compiles

        if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
            dummy
            Text(text="Hello, give me your name :)")
        } else {
            dummy
            Text(text="welcome back ${readNameFromInternalStorage(context)}")
        }

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        OutlinedTextField(
            value=name,
            onValueChange={ name = it },
            label={Text(text="Name")},
            singleLine = true,
            keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Words)
        )

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        Button(
            onClick = {
                dummy = !dummy // modify to trigger recomposition
                name = name.replace(" ", "".replace("\n", ""))
                if(name == "") {
                    Toast.makeText(context, "name invalid", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(context, "name (${name}) saved :D", Toast.LENGTH_SHORT).show()
                    saveNameToInternalStorage(name, context)
                }
            },
        )
        {
            Text(text="save")
        }

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        Button(
            onClick = {
                dummy = !dummy // The same here
                if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
                    Toast.makeText(context, "you need to give me a name first ;)", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(
                        context,
                        "the name is: '${readNameFromInternalStorage(context)}'",
                        Toast.LENGTH_SHORT
                    ).show()
                }

            },
        )
        {
            Text(text="check")
        }

        Spacer(modifier = Modifier
            .height(10.dp)
            .fillMaxWidth())

        Button(onClick = {
                dummy = !dummy //That's all
                cleanNameData(context)
                      }) {
            Text("Remove name")
        }

    }

}

Да, это надо сделать.

Эй, вам не нужно вставлять весь код. Просто предоставьте необходимые биты, и мы попросим больше, если потребуется. Спасибо,

avatar
Rajesh
8 августа 2021 в 18:40
-1

В приведенном ниже блоке кода вы нигде не используете переменную name

if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
    Text(text="Hello, give me your name :)")
} else {
    Text(text="welcome back ${readNameFromInternalStorage(context)}")
}

Таким образом, изменение значения name не повлияет на рекомпозицию. Если вы действительно хотите, вам нужно (вроде) иметь remember{readNameFromInternalStorage(context)} чтобы рекомпозиция повлияла на текст.

Richard Onslow Roper
8 августа 2021 в 18:56
0

Вы уверены, что добавление remember действительно вызовет перекомпоновку?

Richard Onslow Roper
9 августа 2021 в 11:34
0

Да сэр? Уверены ли вы?

Rajesh
11 августа 2021 в 07:20
0

вот 2 вещи: 1. запомните name и используйте его в Text(). 2. помните readNameFromInternalStorage, чтобы он запускал перекомпоновку при изменении значения. попробуй с ним.

Richard Onslow Roper
11 августа 2021 в 07:25
0

Нет, не будет. remember не играет роли в запуске рекомпозиции

Rajesh
12 августа 2021 в 12:04
0

Я ответил на вопрос пользователя о том, как добиться изменения этого текста, а не о том, чтобы вызвать рекомпозицию или нет.

Richard Onslow Roper
12 августа 2021 в 12:18
0

Но вопрос был только в том, чтобы вызвать рекомпозицию, но в любом случае remember даже не «добился бы изменения этого текста». Подождите, вы сказали, что речь не идет о запуске перекомпоновки, но если нет, то как, черт возьми, текст «изменится»? Рекомпозиция, я полагаю?