Skip to content

Fix blank Settings screen after process recreation - #576

Open
M1n9yu23 wants to merge 7 commits into
google:mainfrom
M1n9yu23:m1n9yu23/fix-settings-blank-after-process-recreation
Open

M1n9yu23 wants to merge 7 commits into
google:mainfrom
M1n9yu23:m1n9yu23/fix-settings-blank-after-process-recreation

Conversation

@M1n9yu23

Copy link
Copy Markdown

Description

Fixes #572.

SettableConstraintsRepositoryImpl was only ever populated by PreviewViewModel, so when the process was recreated with SettingsScreen on top of the back stack, systemConstraints stayed null and the settings list never rendered.

  • CameraXCameraSystemRepository now implements ConstraintsRepository, and SettableConstraintsRepository and ConstraintsModule are removed. CameraModule scopes the concrete repository and provides both CameraSystemRepository and ConstraintsRepository from it, so they resolve to the same activity-retained instance. PreviewViewModel no longer copies constraints; it reads cameraSystemRepository.systemConstraints directly for captureUiState / debugUiState.
  • Camera system initialization now starts when systemConstraints is first accessed. I went with this instead of the onStart + stateIn approach I described in the issue: stateIn launches a sharing coroutine in the @Singleton @DefaultCoroutineScope, and that coroutine stays alive regardless of the SharingStarted policy, so it outlives the @ActivityRetainedScoped repository. runTest caught that as an UncompletedCoroutinesError on the first attempt. initializationDeferred.start() needs no extra coroutine and exposes the camera system's own StateFlow as is.
  • SettingsUiState.Disabled is renamed to Loading, and SettingsScreen renders a progress indicator for it instead of an empty Column.
  • The CAMERA check moved from previewScreen into a CameraPermissionGuard in JcaApp. It covers whichever destination is restored, clears the back stack before routing to PermissionsRoute, and does nothing while the current destination is already the permissions screen.
  • This came up while testing the restore path: once Settings rendered, changes made there never reached the camera system, since the camera had already been initialized before PreviewViewModel existed. PreviewViewModel now diffs default settings against the snapshot the camera system was initialized with, rather than against its own first emission. The snapshot is exposed as CameraSystemRepository.getInitialDefaultCameraAppSettings().

As discussed in the issue, SettingsScreen now initializes CameraX on its own.

SettableConstraintsRepositoryImpl was also doubling as a fake in PreviewViewModelTest, CameraAppSettingsViewModelTest and CaptureUiStateAdapterTest. Those now use a new FakeConstraintsRepository in :data:settings:testing or FakeCameraSystem.setSystemConstraints.

Testing

  • ./gradlew test
  • ./gradlew spotlessCheck
  • ./gradlew assembleStableDebug
  • Manually verified the three paths from the issue on an emulator: adb shell am kill, "Don't keep activities", and revoking CAMERA in system settings.
  • Added unit tests for the lazy initialization, the Loading state, and default settings changed before PreviewViewModel exists.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the camera constraints repository and permission handling. It removes the settable constraints repository, simplifies ConstraintsRepository to be served directly by CameraXCameraSystemRepository, and introduces a centralized CameraPermissionGuard in JcaApp to handle camera permission revocation. Additionally, the settings screen now displays a loading indicator while constraints are being loaded. The review feedback suggests restricting the visibility of the new Dagger provider methods in CameraModule to internal and adding KDoc documentation for the new isPermissionsRoute extension function.

@Provides
@ActivityRetainedScoped
fun providesCameraSystemRepository(
fun providesCameraXCameraSystemRepository(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

According to the repository style guide, we should use the most restrictive visibility modifier possible. Since CameraModule is an internal object, this provides method should be marked internal.

Suggested change
fun providesCameraXCameraSystemRepository(
internal fun providesCameraXCameraSystemRepository(
References
  1. Ensure all new functions, properties, and classes use the most restrictive visibility modifier possible (e.g., private, internal) while still allowing necessary access. Avoid public visibility unless explicitly required for external API exposure. (link)

Comment on lines +65 to +73
@Provides
fun providesCameraSystemRepository(
repository: CameraXCameraSystemRepository
): CameraSystemRepository = repository

@Provides
fun providesConstraintsRepository(
repository: CameraXCameraSystemRepository
): ConstraintsRepository = repository

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Following the repository style guide, these new provides methods should also be marked internal to restrict their visibility appropriately.

Suggested change
@Provides
fun providesCameraSystemRepository(
repository: CameraXCameraSystemRepository
): CameraSystemRepository = repository
@Provides
fun providesConstraintsRepository(
repository: CameraXCameraSystemRepository
): ConstraintsRepository = repository
@Provides
internal fun providesCameraSystemRepository(
repository: CameraXCameraSystemRepository
): CameraSystemRepository = repository
@Provides
internal fun providesConstraintsRepository(
repository: CameraXCameraSystemRepository
): ConstraintsRepository = repository
References
  1. Ensure all new functions, properties, and classes use the most restrictive visibility modifier possible (e.g., private, internal) while still allowing necessary access. Avoid public visibility unless explicitly required for external API exposure. (link)

}
}

fun NavDestination.isPermissionsRoute(): Boolean = route?.substringBefore('?') == BASE_ROUTE_DEF

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

According to the repository style guide, all non-private members must have KDoc documentation. Please add a KDoc comment for isPermissionsRoute.

/**
 * Returns whether this [NavDestination] corresponds to the permissions route.
 */
fun NavDestination.isPermissionsRoute(): Boolean = route?.substringBefore('?') == BASE_ROUTE_DEF
References
  1. Document all non-private members: All non-private classes, functions, and composables must have KDoc documentation. (link)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Settings screen renders blank after process recreation due to uninitialized CameraSystemConstraints

1 participant