Skip to content

Add Empty-State UI and Retry Actions for Summary and Prescription Screens#6

Open
Ishaan400 wants to merge 2 commits intoAOSSIE-Org:mainfrom
Ishaan400:fix/empty-state-ui
Open

Add Empty-State UI and Retry Actions for Summary and Prescription Screens#6
Ishaan400 wants to merge 2 commits intoAOSSIE-Org:mainfrom
Ishaan400:fix/empty-state-ui

Conversation

@Ishaan400
Copy link

@Ishaan400 Ishaan400 commented Dec 25, 2025

Add Empty-State UI, Retry Actions, and API Key Fallback Config

This PR improves the user experience and resilience of the app when transcription or Gemini data is missing or fails to load, and also adds robust API key handling for different environments.

  • UI Enhancements: Friendly Empty-State and Retry Support

  • Adds friendly empty-state UIs with icon, title, and helpful message to:

summary_screen.dart

prescription_screen.dart

Each screen includes a Retry button when onRetry is available

Updates main.dart to wire:

onRetry → _processWithGemini(_transcription)

Allows users to regenerate summaries/prescriptions without restarting the app

Config Upgrade: Safer API Key Loading

  • Introduces config.dart:

Loads GEMINI_API_KEY, DEEPGRAM_API_KEY from --dart-define

Falls back to .env values for local dev

Prevents failures in CI/production where .env isn’t bundled

  • Wraps dotenv.load() in safe try-catch to avoid startup crashes

  • Why This Matters

Shows clear UI feedback instead of blank screens on failure

Makes app more resilient and user-friendly with Retry flows

Enables safe, flexible API key access in local + production environments

Improves polish, developer experience, and maintainability

Testing

Verified success/failure flows on local emulator

Confirmed Retry actions re-trigger expected logic

Tested fallback API key logic in debug mode with and without .env

Files Changed

lib/screens/summary_screen.dart

lib/screens/prescription_screen.dart

lib/main.dart

lib/services/chatbot_service.dart

lib/services/config.dart

@coderabbitai
Copy link

coderabbitai bot commented Dec 25, 2025

📝 Walkthrough

Walkthrough

Added resilient env key retrieval utilities and switched API key access to getters. SummaryScreen and PrescriptionScreen gained optional onRetry callbacks and richer empty-state UI with conditional Retry buttons. main.dart wires onRetry to pop the route and re-trigger Gemini processing using the existing transcription.

Changes

Cohort / File(s) Summary
Config / Env utilities
lib/services/config.dart
Added getGeminiApiKey() and getDeepgramApiKey() that prefer --dart-define values and fall back to dotenv.env (returning empty string if absent).
Chatbot Service API key access
lib/services/chatbot_service.dart
Replaced static apiKey field with a getter String get apiKey => getGeminiApiKey(); to defer key retrieval to config utilities.
Main wiring & env loading
lib/main.dart
Added resilient dotenv loading (attempt and continue on failure). Replaced direct dotenv access with getDeepgramApiKey() for Deepgram. Passed onRetry callbacks to SummaryScreen and PrescriptionScreen that pop the route and re-invoke Gemini processing using the existing transcription.
UI: Summary / Prescription screens
lib/screens/summary_screen.dart, lib/screens/prescription_screen.dart
Added optional VoidCallback? onRetry fields and constructor params. Enhanced empty-state UI (icon, title, subtitle) and render a conditional Retry button when onRetry is provided. PrescriptionScreen FAB now disabled when prescription is empty or saving.

Sequence Diagram(s)

sequenceDiagram
  %% Styling note: primary flows in blue, retries in orange
  participant User as User
  participant Screen as Summary/Prescription Screen
  participant Nav as Navigator
  participant Main as main.dart (controller)
  participant Gemini as GeminiService

  User->>Screen: tap "Retry" (onRetry)
  Screen->>Nav: pop()
  Nav->>Main: route popped (resume)
  Main->>Gemini: process transcription (existing data)
  alt success
    Gemini-->>Main: summary/prescription result
    Main->>Screen: navigate to Summary/Prescription (new content)
  else error
    Gemini-->>Main: error
    Main->>Screen: navigate to Summary/Prescription (empty/error state)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 In fields of code I hop with glee,

Retry button springs — pop back, set free.
Transcription waits, Gemini hums anew,
We try once more — the summary grew. ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the main changes: adding empty-state UI components and retry action callbacks to the Summary and Prescription screens, which are the primary focus of the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
lib/main.dart (1)

498-500: Apply the same validation and concurrency guard as SummaryScreen retry.

This retry callback has identical issues to the SummaryScreen retry at lines 479-481: no validation of _transcription content and no concurrency guard.

🔎 Proposed fix
  onRetry: () {
+   if (_isProcessing) return;
+   if (_transcription.isEmpty || _transcription == 'No speech detected') {
+     ScaffoldMessenger.of(context).showSnackBar(
+       const SnackBar(content: Text('No valid transcription to retry')),
+     );
+     return;
+   }
    Navigator.pop(context);
    _processWithGemini(_transcription);
  },
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67b9681 and f4a0fd7.

📒 Files selected for processing (3)
  • lib/main.dart
  • lib/screens/prescription_screen.dart
  • lib/screens/summary_screen.dart
🔇 Additional comments (5)
lib/screens/summary_screen.dart (2)

6-8: LGTM! Clean API extension with optional retry callback.

The optional onRetry parameter is well-designed, maintaining backward compatibility while enabling retry functionality.


59-128: Well-structured empty state UI with proper null safety.

The empty state implementation is clean and defensive:

  • Conditional Retry button properly guarded by onRetry != null check
  • Friendly messaging with icon and instructions
  • Markdown rendering preserved for non-empty content

Note: With the current navigation logic in main.dart (line 473 enables the button only when _summaryContent.isNotEmpty), this empty state is unreachable through normal user flow. However, this defensive UI is good future-proofing if navigation logic changes or direct navigation is added later.

lib/screens/prescription_screen.dart (3)

10-12: LGTM! Consistent retry API extension.

The onRetry parameter follows the same clean pattern as SummaryScreen, maintaining consistency across screens.


207-207: Good defensive check on FAB.

Disabling the save button when prescription.isEmpty prevents users from attempting to save empty content, improving UX consistency.


128-197: Well-implemented empty state matching SummaryScreen pattern.

The empty state UI is properly structured with:

  • Icon and descriptive messaging
  • Conditional Retry button with null safety
  • Preserved Markdown rendering for non-empty content

As with SummaryScreen, this empty state is currently unreachable through normal navigation (button enabled only when _prescriptionContent.isNotEmpty at line 492 in main.dart), but serves as good defensive UI for future changes.

Comment on lines +479 to +481
onRetry: () {
Navigator.pop(context);
_processWithGemini(_transcription);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add validation and concurrency guard before retrying Gemini processing.

The retry callback directly passes _transcription to _processWithGemini without validating its content or checking if processing is already in progress. This can lead to:

  1. Invalid input: _transcription might contain error messages like 'No speech detected', 'Transcription failed', or 'Error during transcription'. Line 228 shows the proper validation that should be replicated here.
  2. Concurrent processing: Users can trigger multiple retries before the previous one completes, causing race conditions and wasted API calls.
🔎 Proposed fix with validation and concurrency guard
  onRetry: () {
+   if (_isProcessing) return;
+   if (_transcription.isEmpty || _transcription == 'No speech detected') {
+     ScaffoldMessenger.of(context).showSnackBar(
+       const SnackBar(content: Text('No valid transcription to retry')),
+     );
+     return;
+   }
    Navigator.pop(context);
    _processWithGemini(_transcription);
  },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onRetry: () {
Navigator.pop(context);
_processWithGemini(_transcription);
onRetry: () {
if (_isProcessing) return;
if (_transcription.isEmpty || _transcription == 'No speech detected') {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No valid transcription to retry')),
);
return;
}
Navigator.pop(context);
_processWithGemini(_transcription);
},
🤖 Prompt for AI Agents
In lib/main.dart around lines 479-481, the onRetry callback pops the dialog and
calls _processWithGemini(_transcription) without validating the transcription or
checking for ongoing work; replicate the validation used at line 228 (reject
values like 'No speech detected', 'Transcription failed', 'Error during
transcription', empty or null) and add a concurrency guard (e.g., check an
_isProcessing boolean, return early if true, set it true before calling
_processWithGemini and false in finally/whenComplete). Also only call
Navigator.pop and start processing if validation passes and processing is not
already in progress.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
lib/main.dart (2)

486-489: Validation and concurrency guard still missing in retry callback.

This issue was already flagged in a previous review. The onRetry callback should validate _transcription content (rejecting error messages like 'No speech detected', 'Transcription failed', etc.) and check _isProcessing to prevent concurrent API calls.


505-508: Same validation and concurrency concern applies here.

The PrescriptionScreen retry callback has the same issues as the SummaryScreen callback—missing input validation and concurrency guard.

🧹 Nitpick comments (1)
lib/services/config.dart (1)

4-15: Consider logging a warning when API keys are missing.

The fallback logic is correct and the implementation is clean. However, returning an empty string when both sources fail could lead to silent failures downstream (e.g., 401 errors from Deepgram/Gemini that may be harder to debug).

Consider adding a debug log when falling back to an empty string:

🔎 Optional: Add debug logging for missing keys
+import 'dart:developer' as developer;
 import 'package:flutter_dotenv/flutter_dotenv.dart';

 /// Return GEMINI API key from --dart-define or .env as fallback.
 String getGeminiApiKey() {
   const envValue = String.fromEnvironment('GEMINI_API_KEY');
   if (envValue.isNotEmpty) return envValue;
-  return dotenv.env['GEMINI_API_KEY'] ?? '';
+  final dotenvValue = dotenv.env['GEMINI_API_KEY'] ?? '';
+  if (dotenvValue.isEmpty) {
+    developer.log('GEMINI_API_KEY not found in dart-define or .env');
+  }
+  return dotenvValue;
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4a0fd7 and cfba4e0.

📒 Files selected for processing (3)
  • lib/main.dart
  • lib/services/chatbot_service.dart
  • lib/services/config.dart
🔇 Additional comments (1)
lib/main.dart (1)

20-26: Good resilient .env loading pattern.

The try-catch approach gracefully handles missing .env files in production builds while logging for debugging purposes.

Future<void> _transcribeAudio() async {
try {
final apiKey = dotenv.env['DEEPGRAM_API_KEY'] ?? '';
final apiKey = getDeepgramApiKey();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing import for getDeepgramApiKey will cause a compile error.

The function getDeepgramApiKey() is called but not imported from config.dart. This will fail at compile time.

🔎 Proposed fix

Add the import at the top of the file with the other service imports:

 import 'services/chatbot_service.dart';
 import 'screens/transcription_detail_screen.dart';
 import 'screens/summary_screen.dart';
 import 'screens/prescription_screen.dart';
+import 'services/config.dart';

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In lib/main.dart around line 196, the call to getDeepgramApiKey() is missing its
import which will cause a compile error; add the import for the symbol from its
defining file (config.dart) alongside the other service imports at the top of
lib/main.dart so the function is recognized by the compiler.

Comment on lines 7 to +9
// Get API key from .env file
final String apiKey = dotenv.env['GEMINI_API_KEY'] ?? '';
// Get API key using config fallback (dart-define > .env)
String get apiKey => getGeminiApiKey();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing import for getGeminiApiKey will cause a compile error.

The getter references getGeminiApiKey() but the function is not imported from config.dart. This will fail at compile time.

Also, the flutter_dotenv import on line 4 is now unused and the comment is stale.

🔎 Proposed fix
 import 'dart:convert';
 import 'dart:developer' as developer;
 import 'package:http/http.dart' as http;
-import 'package:flutter_dotenv/flutter_dotenv.dart';
+import 'config.dart';

 class ChatbotService {
-  // Get API key from .env file
     // Get API key using config fallback (dart-define > .env)
     String get apiKey => getGeminiApiKey();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Get API key from .env file
final String apiKey = dotenv.env['GEMINI_API_KEY'] ?? '';
// Get API key using config fallback (dart-define > .env)
String get apiKey => getGeminiApiKey();
import 'dart:convert';
import 'dart:developer' as developer;
import 'package:http/http.dart' as http;
import 'config.dart';
class ChatbotService {
// Get API key using config fallback (dart-define > .env)
String get apiKey => getGeminiApiKey();
🤖 Prompt for AI Agents
In lib/services/chatbot_service.dart around lines 7 to 9, the getter references
getGeminiApiKey() but the function isn't imported which causes a compile error;
add the proper import for the file that exports getGeminiApiKey (e.g., import
'package:your_package/config.dart'; or the correct relative path) at the top of
the file, remove the now-unused flutter_dotenv import, and update or remove the
stale comment so imports and comments match the code.

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

Comments