Skip to content

Moving APIs in PreviewViewModel to Controller classes#482

Open
davidjiagoogle wants to merge 9 commits intomainfrom
david/previewViewModelCallbacks
Open

Moving APIs in PreviewViewModel to Controller classes#482
davidjiagoogle wants to merge 9 commits intomainfrom
david/previewViewModelCallbacks

Conversation

@davidjiagoogle
Copy link
Collaborator

@davidjiagoogle davidjiagoogle commented Feb 20, 2026

Interfaces QuickSettingsController, CaptureController, CaptureScreenController, SnackBarController, ZoomController, and DebugController are created. They contain APIs that were originally implemented in the view models. A default implementation of their APIs is made for each interface so that view model can be simplified

PreviewViewModel now only contains instances of these controller and has no APIs itself.

@gemini-code-assist
Copy link
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@temcguir
Copy link
Collaborator

/gemini review

Copy link
Contributor

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

Choose a reason for hiding this comment

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

Code Review

The pull request successfully refactors the PreviewViewModel by extracting various UI event handling logic into dedicated callback data classes: QuickSettingsCallbacks, CaptureCallbacks, SnackBarCallbacks, and DebugCallbacks. This significantly simplifies the PreviewViewModel and improves modularity, making the code easier to understand and maintain. The changes are well-implemented, and the new callback structures enhance code organization and reusability. The test file PreviewViewModelTest.kt has also been updated to reflect these changes, ensuring continued test coverage.

@davidjiagoogle davidjiagoogle changed the title Moving APIs in PreviewViewModel to Callbacks data classes Moving APIs in PreviewViewModel to Controller classes Feb 25, 2026
@temcguir
Copy link
Collaborator

/gemini review

@gemini-code-assist
Copy link
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@temcguir
Copy link
Collaborator

/gemini review

Copy link
Contributor

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

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 is a significant and well-executed refactoring that decomposes the large PreviewViewModel into several smaller, single-responsibility controller classes. This greatly improves the architecture by enhancing modularity, testability, and overall code clarity. The changes are consistently applied across the UI components, ViewModels, and tests. My review includes a few suggestions: fixing a minor copy-paste bug in CaptureControllerImpl, adding KDoc to the new controller APIs to improve documentation, and a couple of minor stylistic improvements in PreviewScreen.kt to make the code more idiomatic.

Copy link
Collaborator

@temcguir temcguir left a comment

Choose a reason for hiding this comment

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

This is a good start. There are definitely a few things that could be cleaned up later (such as the zoom controller). If you can address the comments I've added we could merge this and make smaller clean-up PRs to address those later.

}
}

class MockQuickSettingsController : QuickSettingsController {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Call this "NoOpQuickSettingsController". We should follow this naming convention for any of the @Preview composables..

Comment on lines +35 to +36
fun postCurrentMediaToMediaRepository(
viewModelScope: CoroutineScope,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of passing in a viewModelScope, you should just make this a suspend function and have the viewModelScope.launch within the ViewModel wrapping this suspend function.

Comment on lines +56 to +61
/**
* Pauses or resumes video recording.
*
* @param shouldBePaused Whether the recording should be paused.
*/
fun setPaused(shouldBePaused: Boolean)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be in CaptureController.

Comment on lines +39 to +44
/**
* Enables or disables audio recording.
*
* @param shouldEnableAudio Whether audio should be enabled.
*/
fun setAudioEnabled(shouldEnableAudio: Boolean)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be in CaptureController.

Comment on lines +45 to +54

/**
* Updates the UI with the last captured media.
*/
fun updateLastCapturedMedia()

/**
* Transfers the media from the image well to the repository.
*/
fun imageWellToRepository()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can these be moved to an ImageWellController?

private val saveMode: SaveMode,
private val externalCaptureMode: ExternalCaptureMode,
private val externalCapturesCallback: () -> Pair<SaveLocation, IntProgress?>,
private val captureEvents: Channel<CaptureEvent>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this need to be an input to the constructor? Can it just be a val on CaptureController? (likely as a ReceiveChannel in the interface).

*/
class CaptureControllerImpl(
private val trackedCaptureUiState: MutableStateFlow<TrackedCaptureUiState>,
private val viewModelScope: CoroutineScope,
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's generally considered bad practice to pass in CoroutineScope to constructors. At a minimum this should be a CoroutineContext from which we create a scope within the controller. See go/android-api-guidelines-async#coroutines-constructor-context for more info.

Comment on lines +24 to +37
/**
* Sets the display rotation.
*
* @param deviceRotation The device rotation to set.
*/
fun setDisplayRotation(deviceRotation: DeviceRotation)

/**
* Initiates a tap-to-focus action at the given coordinates.
*
* @param x The x-coordinate of the tap.
* @param y The y-coordinate of the tap.
*/
fun tapToFocus(x: Float, y: Float)
Copy link
Collaborator

Choose a reason for hiding this comment

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

These can be moved into CameraController.

private val trackedCaptureUiState: MutableStateFlow<TrackedCaptureUiState>,
private val viewModelScope: CoroutineScope,
private val cameraSystem: CameraSystem,
private val mediaRepository: MediaRepository,
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be good if we could remove the dependency on MediaRepository here. We should only have a MediaRepository dependency in the ViewModel. Maybe this can be replaced with a callback that can be supplied at creation time which will call the MediaRepository within the ViewModel instead of within the controller.

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.

2 participants