Skip to content

refactor(ai-logic): split samples into various ViewModels#2767

Open
thatfiredev wants to merge 24 commits intomasterfrom
rpf/chatviewmodel-refactor
Open

refactor(ai-logic): split samples into various ViewModels#2767
thatfiredev wants to merge 24 commits intomasterfrom
rpf/chatviewmodel-refactor

Conversation

@thatfiredev
Copy link
Member

The goal is to make these samples more readable by splitting them into various ViewModels - each feature has its own ViewModel.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the sample application's architecture by decomposing its AI features into distinct ViewModel components. This change aims to enhance the modularity and maintainability of the codebase, making it easier for developers to understand and extend individual AI functionalities. The updated navigation system now leverages these new ViewModels, streamlining the process of integrating and showcasing various AI capabilities within the app.

Highlights

  • Refactored Sample Structure: The sample application's AI features have been refactored into individual ViewModel classes, significantly improving code organization and readability. Each feature now has its own dedicated ViewModel.
  • Updated Navigation Logic: The main activity's navigation has been updated to dynamically handle routes and instantiate the appropriate ViewModel for each sample, replacing the previous hardcoded navigation logic.
  • Enhanced README Documentation: The README file has been updated with more detailed setup instructions and a reorganized 'Features' section, categorizing samples and linking directly to their new ViewModel implementations.
  • Centralized UI State Management: Abstract base ViewModels (ChatViewModel, ImagenViewModel) and new UI state sealed interfaces (ChatUiState, ImagenUiState, ServerPromptUiState, SvgUiState) have been introduced to standardize and centralize UI state management across different sample types.
  • Removed Monolithic Sample Definition: The large FirebaseAISamples.kt file, which previously contained all sample definitions, has been removed. Sample metadata is now defined in ui/navigation/FirebaseAISamples.kt and linked to their respective ViewModels.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • firebase-ai/README.md
    • Updated setup and configuration prerequisites.
    • Reorganized the 'Features' section to list samples by category (Text/Chat, Image analysis/generation, Audio analysis, Video analysis, Live API, Document analysis).
    • Updated sample links to point to their new ViewModel files.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt
    • Removed, as sample definitions are now distributed and managed through a new navigation structure.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt
    • Removed unused imports for Android permissions and specific feature routes.
    • Updated TopAppBar title content color to onPrimaryContainer.
    • Refactored navigation to iterate through FIREBASE_AI_SAMPLES and dynamically create composables based on ScreenType.
    • Instantiated ViewModels using viewModel(modelClass = sample.viewModelClass!!.java) for each sample route.
    • Simplified onDestinationChangedListener lambda.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt
    • Removed SavedStateHandle dependency for sampleId and sample object.
    • Hardcoded LiveSession configuration with googleAI() backend, model name, and fetchWeather function declaration.
    • Added StreamRealtimeRoute and StreamRealtimeVideoRoute as serializable objects.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/CameraView.kt
    • Renamed to firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/CameraView.kt.
    • Updated LocalLifecycleOwner import from androidx.compose.ui.platform to androidx.lifecycle.compose.
    • Modified Preview builder to use it.surfaceProvider = previewView.surfaceProvider instead of it.setSurfaceProvider.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt
    • Renamed to firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/StreamRealtimeScreen.kt.
    • Updated imports to reflect new package structure and removed StreamRealtimeRoute serialization.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeVideoScreen.kt
    • Renamed to firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/StreamRealtimeVideoScreen.kt.
    • Updated imports to reflect new package structure and removed StreamRealtimeVideoRoute serialization.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt
    • Removed, as image editing modes are now implicitly handled within specific Imagen ViewModels.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenGenerationViewModel.kt
    • Added new ViewModel for Imagen 4 image generation, extending ImagenViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenInpaintingViewModel.kt
    • Added new ViewModel for Imagen 3 inpainting, extending ImagenViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenOutpaintingViewModel.kt
    • Added new ViewModel for Imagen 3 outpainting, extending ImagenViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenScreen.kt
    • Renamed to firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenScreen.kt.
    • Updated imports to reflect new package structure and removed ImagenRoute serialization.
    • Refactored to accept ImagenViewModel as a parameter and use its uiState for rendering images, loading indicators, and error messages.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenStyleTransferViewModel.kt
    • Added new ViewModel for Imagen 3 style transfer, extending ImagenViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenSubjectReferenceViewModel.kt
    • Added new ViewModel for Imagen 3 subject reference, extending ImagenViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenTemplateViewModel.kt
    • Added new ViewModel for Imagen server prompt templates, extending ImagenViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt
    • Refactored into an abstract base class, defining common properties and abstract performGeneration method.
    • Updated generateImages to manage _uiState (Loading, Success, Error) and delegate generation logic.
    • Modified attachFile and selectOption to update the new _uiState.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgScreen.kt
    • Renamed to firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/SvgScreen.kt.
    • Updated imports to reflect new package structure and removed SvgRoute serialization.
    • Refactored to accept SvgViewModel as a parameter and use its uiState for rendering SVGs, loading indicators, and error messages.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgViewModel.kt
    • Removed, as its functionality is now encapsulated in firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/SvgViewModel.kt.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioSummarizationViewModel.kt
    • Added new ViewModel for audio summarization, extending ChatViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioTranslationViewModel.kt
    • Added new ViewModel for audio translation, extending ChatViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt
    • Renamed to firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ChatScreen.kt.
    • Updated imports to reflect new package structure and removed ChatRoute serialization.
    • Refactored to accept ChatViewModel as a parameter and use its uiState for rendering messages, loading indicators, and error messages.
    • Moved Attachment data class to ChatUiState.kt.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatViewModel.kt
    • Refactored into an abstract base class, managing _uiState for messages and attachments.
    • Introduced abstract performSendMessage for subclasses to implement specific AI logic.
    • Centralized error handling and grounding compliance checks in validateAndDisplayResponse.
    • Removed direct SavedStateHandle dependency and specific function-calling logic, delegating to subclasses.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/CourseRecommendationsViewModel.kt
    • Added new ViewModel for course recommendations, extending ChatViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/DocumentComparisonViewModel.kt
    • Added new ViewModel for document comparison, extending ChatViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/GoogleSearchGroundingViewModel.kt
    • Added new ViewModel for Google Search grounding, extending ChatViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageBlogCreatorViewModel.kt
    • Added new ViewModel for image blog creation, extending ChatViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageGenerationViewModel.kt
    • Added new ViewModel for Gemini 2.5 Flash image generation, extending ChatViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ServerPromptTemplateViewModel.kt
    • Added new ViewModel for Gemini server prompt templates, managing its own UI state.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/SvgViewModel.kt
    • Added new ViewModel for SVG generation, managing its own UI state.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt
    • Renamed to firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ServerPromptScreen.kt.
    • Updated imports to reflect new package structure and removed TextGenRoute serialization.
    • Refactored to accept ServerPromptTemplateViewModel as a parameter and use its uiState for rendering generated text, loading indicators, and error messages.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt
    • Removed, as its functionality is now encapsulated in ServerPromptTemplateViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ThinkingChatViewModel.kt
    • Added new ViewModel for dynamic thinking chat, extending ChatViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TravelTipsViewModel.kt
    • Added new ViewModel for travel tips, extending ChatViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoHashtagGeneratorViewModel.kt
    • Added new ViewModel for video hashtag generation, extending ChatViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoSummarizationViewModel.kt
    • Added new ViewModel for video summarization, extending ChatViewModel.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/WeatherChatViewModel.kt
    • Added new ViewModel for weather chat with function calling, extending ChatViewModel and handling fetchWeather.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ChatUiState.kt
    • Added new file defining Attachment data class, UiChatMessage data class, and ChatUiState sealed interface for managing UI states in chat screens.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenUiState.kt
    • Added new file defining ImagenUiState sealed interface for managing UI states in Imagen screens.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ServerPromptUiState.kt
    • Added new file defining ServerPromptUiState sealed interface for managing UI states in server prompt screens.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/SvgUiState.kt
    • Added new file defining SvgUiState sealed interface for managing UI states in SVG screens.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt
    • Added new file defining FIREBASE_AI_SAMPLES list, mapping each sample to its ScreenType and ViewModel class.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/MainMenuScreen.kt
    • Updated to use the new FIREBASE_AI_SAMPLES list for displaying available samples.
  • firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt
    • Refactored Sample data class to remove AI-specific parameters (modelName, backend, initialPrompt, etc.).
    • Added ScreenType enum and viewModelClass property to Sample for dynamic ViewModel instantiation.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@thatfiredev thatfiredev marked this pull request as draft March 6, 2026 20:20
Copy link

@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 great architectural refactoring, splitting monolithic sample definitions and ViewModels into smaller, feature-specific ones. This significantly improves code organization, readability, and maintainability. The adoption of UiState sealed interfaces for screen state management is another excellent change that aligns with modern Android development best practices. I've identified a few areas for improvement, including a critical issue with the BidiViewModel that appears to break a feature, along with some opportunities to enhance code safety and consistency.

val errorMessage by svgViewModel.errorMessage.collectAsStateWithLifecycle()
val isLoading by svgViewModel.isLoading.collectAsStateWithLifecycle()
val generatedSvgs by svgViewModel.generatedSvgs.collectAsStateWithLifecycle()
var prompt by rememberSaveable { mutableStateOf("A kitten") }

Choose a reason for hiding this comment

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

medium

The initial prompt is hardcoded in the UI layer. For better separation of concerns and consistency with the other refactored screens, this value should be defined in the SvgViewModel and consumed here.

To fix this, you can add val initialPrompt: String = "a kitten" to SvgViewModel and then update this line to use svgViewModel.initialPrompt.

Suggested change
var prompt by rememberSaveable { mutableStateOf("A kitten") }
var prompt by rememberSaveable { mutableStateOf(svgViewModel.initialPrompt) }

@thatfiredev
Copy link
Member Author

/gemini review

@thatfiredev thatfiredev marked this pull request as ready for review March 6, 2026 23:22
Copy link

@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 is an excellent and extensive refactoring that greatly improves the project's structure and maintainability. Splitting the monolithic sample file into feature-specific ViewModels, introducing abstract base classes for ChatViewModel and ImagenViewModel, and using sealed interfaces for UI state are all fantastic architectural improvements. The code is now much more organized and easier to follow. I've found a few issues, including a critical one related to blocking the main thread, that should be addressed to ensure the app's stability and performance.

)
),
)
runBlocking { liveSession = liveModel.connect() }

Choose a reason for hiding this comment

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

critical

Using runBlocking in the init block is a critical issue. The init block of a ViewModel is executed on the main thread, and runBlocking will block this thread while waiting for the network operation (liveModel.connect()) to complete. This will cause the UI to freeze and can lead to an Application Not Responding (ANR) error.

Please perform this operation asynchronously using viewModelScope on a background dispatcher. You will need to manage the connection state (e.g., with a StateFlow) to ensure liveSession is initialized before it's used by functions like startConversation().

A similar issue exists in StreamVideoViewModel.kt.

Comment on lines +77 to +79
val city = functionCall.args["city"]!!.jsonPrimitive.content
val state = functionCall.args["state"]!!.jsonPrimitive.content // Fixed state retrieval
val date = functionCall.args["date"]!!.jsonPrimitive.content

Choose a reason for hiding this comment

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

high

Using the non-null assertion operator (!!) on arguments from the model's function call is risky. If the model's response doesn't include the expected arguments for any reason, the app will crash with a NullPointerException. It's much safer to use safe calls (?.) and gracefully handle cases where arguments might be missing.

Suggested change
val city = functionCall.args["city"]!!.jsonPrimitive.content
val state = functionCall.args["state"]!!.jsonPrimitive.content // Fixed state retrieval
val date = functionCall.args["date"]!!.jsonPrimitive.content
val city = functionCall.args["city"]?.jsonPrimitive?.content
val state = functionCall.args["state"]?.jsonPrimitive?.content
val date = functionCall.args["date"]?.jsonPrimitive?.content

currentState: ImagenUiState.Success
): ImagenGenerationResponse<ImagenInlineImage> {
return try {
templateImagenModel.generateImages("imagen-basic", mapOf("prompt" to inputText))

Choose a reason for hiding this comment

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

medium

The template ID "imagen-basic" and key "prompt" are hardcoded as magic strings. This makes the code harder to read and maintain, and increases the risk of typos. It would be better to extract these into private const val at the top of the file or in a companion object.

Suggested change
templateImagenModel.generateImages("imagen-basic", mapOf("prompt" to inputText))
templateImagenModel.generateImages(TEMPLATE_ID, mapOf(TEMPLATE_PROMPT_KEY to inputText))

_uiState.value = ServerPromptUiState.Loading
try {
val response = templateGenerativeModel
.generateContent("input-system-instructions", mapOf("customerName" to inputText))

Choose a reason for hiding this comment

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

medium

The template ID "input-system-instructions" and key "customerName" are hardcoded. This makes the code harder to read and maintain. Please extract these magic strings into named constants.

Suggested change
.generateContent("input-system-instructions", mapOf("customerName" to inputText))
.generateContent(TEMPLATE_ID, mapOf(TEMPLATE_CUSTOMER_NAME_KEY to inputText))

fun TextGenScreen(
textGenViewModel: TextGenViewModel = viewModel<TextGenViewModel>()
fun ServerPromptScreen(
viewModel: ServerPromptTemplateViewModel = viewModel()

Choose a reason for hiding this comment

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

medium

The viewModel parameter includes a default viewModel() factory call. Since the ViewModel is now consistently provided from the navigation graph in MainActivity, this default parameter is unnecessary and potentially misleading. Removing it makes the composable's dependency on a parent-provided ViewModel explicit and improves clarity.

Suggested change
viewModel: ServerPromptTemplateViewModel = viewModel()
viewModel: ServerPromptTemplateViewModel


@Composable
fun SvgScreen(
svgViewModel: SvgViewModel = viewModel<SvgViewModel>()

Choose a reason for hiding this comment

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

medium

The svgViewModel parameter has a default value viewModel<SvgViewModel>(). As the ViewModel is now provided from the navigation graph in MainActivity, this default is redundant and can be confusing. It's best practice to remove it to make the dependency explicit.

Suggested change
svgViewModel: SvgViewModel = viewModel<SvgViewModel>()
svgViewModel: SvgViewModel

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.

1 participant