在应用程序启动时使用 Jetpack/Coil 保存并显示个人资料图片

Save and show profile picture with Jetpack/Coil on app startup

我的想法是在那里呈现个人资料图片并允许用户更改它。 为了保存 selected 图片,我使用 SharedPreferences(将 Uri 保存为字符串)。问题是每次启动时图像都不会显示。 共享管理器检索到的值似乎是正确的,但是 SubComposeAsyncImageContent 没有正确显示图片。

个人资料图片可组合:

@Composable
fun ProfilePicture(
    imageUri: String?,
    size: Dp = 50.dp,
    onClick: (String) -> Unit,
) {

    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.GetContent())  { uri: Uri? ->
        onClick(uri.toString())
    }

    if (imageUri != null) {
        Log.e("ProfilePicture", imageUri)
    }

    SubcomposeAsyncImage(
        model = imageUri,
        contentDescription = "",
        modifier = Modifier.clickable { launcher.launch("image/*") }
    ) {
        val state = painter.state
        Log.e("ProfilePicState", "${state}")
        if (state is AsyncImagePainter.State.Loading || state is AsyncImagePainter.State.Error) {
            CircularProgressIndicator()
        } else {
            SubcomposeAsyncImageContent()
        }

    }
}

想法是 imageUri 作为参数从配置文件屏幕(包含 ProfilePicture)传递。配置文件屏幕从 viewModel 获取此值,它可以访问 sharedPreferences。

ProfileScreen.kt:

@Composable
fun ProfileScreen(viewModel: ProfileViewModel) {

    var profileUri by rememberSaveable {
        mutableStateOf(viewModel.getProfilePicURI())
    }

    Log.w("ProfileScreen", profileUri)

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        ProfilePicture(
            imageUri = profileUri,
            size = 150.dp,
            onClick = {
                viewModel.onEvent(ProfileEvents.OnUpdateProfilePic(it))
                profileUri = viewModel.getProfilePicURI()
            }
        )
    }
}

最后,viewModel

class ProfileViewModel(val preferenceManager: PreferenceManager): ViewModel() {

    fun getProfilePicURI(): String {
        return preferenceManager.getProfilePic()
    }

    fun onEvent(event: ProfileEvents) {
        when (event) {
            is ProfileEvents.OnUpdateProfilePic -> {
                // update the sharedpreference
                preferenceManager.setProfilePic(event.newUri)
                Log.e("ProfileVM", "uri stored: ${getProfilePicURI()}")

            }
        }
    }
}

如前所述,代码在应用程序中运行,即我可以更改个人资料图片,即使我返回个人资料屏幕时它也会被记住,但在每次启动时,画家无法加载图像,即使似乎发送了正确的资源。

日志如下所示:

2022-04-27 09:29:45.174 12502-12502/com.example.insurance W/ProfileScreen: content://com.android.providers.media.documents/document/image%3A96
2022-04-27 09:29:45.182 12502-12502/com.example.insurance E/ProfilePicture: content://com.android.providers.media.documents/document/image%3A96
2022-04-27 09:29:45.258 12502-12502/com.example.insurance E/ProfilePicState: Loading(painter=null)
2022-04-27 09:29:45.274 12502-12502/com.example.insurance E/ProfilePicState: Loading(painter=null)
2022-04-27 09:29:45.278 12502-12502/com.example.insurance E/ProfilePicState: Error(painter=null, result=coil.request.ErrorResult@bfc77785)

使用后退按钮退出应用程序有效,在重新创建时,个人资料照片就在那里。通过任务任务管理器销毁它会触发错误行为。

当我启动应用程序时,显示个人资料图片的唯一方法是 select 不同的图像,即如果我 select 之前 select 编辑的 img 它不会显示。一旦我选择一个新的,它就会再次显示

Android 的最新版本仅在当前应用程序会话期间提供对文件的访问权限 - 重新启动后您不再有权读取相同的 URI。

文件仍然存在:如果您再次选择它,您将获得相同的 URI,并且也可以访问,但在您的情况下,相同的 URI 不会触发重组,这就是您不能的原因查看更新。

即使您可以访问 URI,也不能直接访问,您只能从 context.contentResolver 获取输入流。这就是 Coil 在显示它时在后台为您所做的,但是要复制它,您必须自己管理它。

输出文件应该在 App-specific storage.

val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.GetContent()
)  { newUri: Uri? ->
    if (newUri == null) return@rememberLauncherForActivityResult

    val input = context.contentResolver.openInputStream(newUri) ?: return@rememberLauncherForActivityResult
    val outputFile = context.filesDir.resolve("profilePic.jpg")
    input.copyTo(outputFile.outputStream())
    uri = outputFile.toUri()
}

请注意,我在这里使用相同的文件名,因此如果您连续 select 两个不同的图像,您将不会看到从第一张到第二张的更新,因为输出 URI 是还是一样。如何解决这个问题取决于你,例如在文件名中添加一个 UUID 这样每次都是一个唯一的 URI,只是不要忘记删除旧的。