我是一个Android开发的初学者,我不知道如何解决这个问题. 有一个简单的视图模型,它包含一个变量:

var testQuestionsAmount by mutableStateOf(10)
        private set

我在主屏幕中使用这个变量,一切正常,但我也试图在另一个屏幕中使用它作为导航参数,从DB获得一定数量的问题,并且出于某种原因,它会无限更新,导致无限的问题生成.

//For some reason, this updates infinitely
@Composable
fun GeneratedTestScreen(questionsAmount: Int) {
    val context = LocalContext.current
    val questionRepository by lazy {
        QuestionRepository(QuestionDatabase.getInstance(context).dao)
    }
    val questionsList = questionRepository.getRandomQuestions(questionsAmount).collectAsState(initial = emptyList()).value
    val currentTestViewModel = viewModel<GeneratedTestScreenViewModel>(
        factory = object : ViewModelProvider.Factory {
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                return GeneratedTestScreenViewModel(questionsList = questionsList) as T
            }
        }
    )
    Log.d("AMOUNT", questionsAmount.toString())
}

导航图中的已生成测试屏幕:

composable(route = Screen.GeneratedTest.route, arguments = listOf(
    navArgument("questionsAmount") {
        type = NavType.IntType 
    }
)) {
    val questionsAmount = it.arguments?.getInt("questionsAmount")
        GeneratedTestScreen(questionsAmount ?: 1)
}

在密封的班级中:

object GeneratedTest : Screen("GeneratedTest/{questionsAmount}", Icons.Rounded.List, "Test") {
        fun putQuestionsAmount(amount: Int): String {
            return "GeneratedTest/$amount"
        }
    }

从主屏幕导航到生成的测试屏幕:

@Composable
fun TestGenerationScreen(navController: NavController) {
    val viewModel = viewModel<TestGenerationScreenViewModel>()

    ...

        Column(
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier
                .weight(1f)
                .fillMaxWidth()
        ) {
            Button(
                shape = RoundedCornerShape(dimensionResource(id = R.dimen.padding_small)),
                colors = ButtonDefaults.buttonColors(colorResource(id = R.color.approval_green)),
                // Navigation from main to GeneratedTestScreen
                onClick = { navController.navigate(Screen.GeneratedTest.putQuestionsAmount(viewModel.testQuestionsAmount)) },
                modifier = Modifier.align(Alignment.CenterHorizontally)
            ) {
                Text(text = stringResource(id = R.string.test_gen_screen_start_test_button_text))
            }
        }
    }
}

我试着用谷歌搜索它,我发现唯一一件事是,在屏幕从后台堆栈中移除后,viewModel会在内存中保留一段时间,但在我的例子中,主屏幕和GeneratedTestScreen仍然在后台堆栈中.这与这个问题完全无关.我还判断了我的房间查询两次,它是正确的.

那么,我怎么才能修复它呢?这种行为的原因是什么?

另外,对不起,我的英语不好.

推荐答案

你不能把questionRepository个电话作为写作的一部分,所以这一行是做什么的:

VAL问题列表=questionRepository.getRandomQuestions(questionsAmount).collectAsState(initial=EmptyList()).值

实际上是启动collectAsState,然后等待查询返回,因此您的第一个合成将导致您使用默认值emptyList().然后,查询实际完成并返回实际结果--这会导致重新组合.但是,因为您还没有使用remember to save your state,所以整个getRandomQuestions调用都是从头开始的.因此,您得到的不是实际的结果,而是一个全新的回调,并且返回到初始状态……等待您的新查询交付结果,从而导致无限循环.

相反,您应该在您的ViewModel中执行所有这些操作.实际上,您的ViewModel需要两样东西--Context来检索QuestionDatabase,以及您通过NavController传递的questionsAmount参数.

对于这两种情况,您实际上根本不需要任何定制ViewModelProvider.Factory,因为这两种情况都可以通过可以传递给您的ViewModel的默认参数立即可用.第一个方法扩展AndroidViewModel,第二个方法传入SavedStateHandle,它会自动填充您的所有参数,而无需手动传递任何内容或调用it.arguments?.getInt("questionsAmount")作为合成的一部分.

因此,您的ViewModel应该如下所示:

class GeneratedTestScreenViewModel(
  application: Application,
  savedStateHandle: SavedStateHandle
) : AndroidViewModel(application) {

  // First get the database from the Application Context
  private val questionDatabase = QuestionDatabase.getInstance(application)

  // And the questionsAmount variable from the SavedStateHandle
  private val questionsAmount: Int = savedStateHandle["questionsAmount"]

  // Now create your repository
  private val questionRepository = QuestionRepository(questionDatabase.dao)

  // And expose your questionsList from the ViewModel
  // making sure it uses stateIn() to only collect from the database
  // while the screen is visible
  val questionsList = questionRepository
    .getRandomQuestions(questionsAmount)
    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
}

使用此设置,您的屏幕将变为:

// Note that you no longer need to pass in the questionsAmount
@Composable
fun GeneratedTestScreen() {
    val currentTestViewModel = viewModel<GeneratedTestScreenViewModel>()

    // We use collectAsStateWithLifecycle() from lifecycle-runtime-compose
    // to pause collection when the screen isn't visible
    val questionsList = viewModel.questionsList.collectAsStateWithLifecycle()

    // Now you can use your list for your Compose UI
}

Android相关问答推荐

Android编写动画在发布版本中崩溃

如何在Android Emulator上从物理设备接收TCP消息

如何在Jetpack Compose中使用Box Composable来实现这种布局?

在卡片上创建圆角底部边框

从未设置实时数据值

从片段导航回来

如何共享没有';t是否存在?(仅信息)在Android?

任务:app:kaptGenerateStubsDebugKotlin执行失败. > 'compileDebugJavaWithJavac' 任务(当前目标是 1.8)

retrofit2.HttpException: HTTP 401

当 EditText 用于在 android studio 中将字符串发送到 firebase 时,仅允许安全调用错误

为一组闪烁的可组合项制作动画,控制同步/定时

从 Jetpack Compose 中的 IconButton 中删除黑色色调

在 Jetpack Compose 中自动滚动后面的项目

无法解析依赖项'com.github.smarteist:autoimageslider:1.4.0-appcompat'

jetpack compose 中的可点击指示是什么?

当我更改 ViewModel var 时,Kotlin + Compose 中的 Composable 不会更新

Jetpack compose 绘制形状

为什么我不能在屏幕外拿任何物体

即使我在单选按钮上明确设置了选中状态,RecyclerView 中的单选按钮也会随机取消选中

Xamarin 获取动态 ListView DataTemplate 中的按钮单击事件数据