在应用程序启动时使用 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,只是不要忘记删除旧的。
我的想法是在那里呈现个人资料图片并允许用户更改它。
为了保存 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,只是不要忘记删除旧的。