Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion patient/android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip


4 changes: 2 additions & 2 deletions patient/android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ pluginManagement {

plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.2.1" apply false
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
id "com.android.application" version "8.6.0" apply false
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
}

include ":app"
43 changes: 29 additions & 14 deletions patient/lib/provider/auth_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class AuthProvider extends ChangeNotifier {

Future<void> _handleWebSignIn() async {
final supabaseUrl = dotenv.env['SUPABASE_URL'] ??
(throw Exception("Supabase URL not found in .env"));
(throw Exception("Supabase URL not found in .env"));

await supabase.auth.signInWithOAuth(
OAuthProvider.google,
Expand All @@ -93,29 +93,44 @@ class AuthProvider extends ChangeNotifier {
}

Future<void> _handleMobileSignIn() async {
final webClientId = dotenv.env['GOOGLE_WEB_CLIENT_ID'] ??
(throw Exception("WEB_CLIENT_ID not found in .env"));
final webClientId = dotenv.env['GOOGLE_WEB_CLIENT_ID'] ??
(throw Exception("WEB_CLIENT_ID not found in .env"));
final iosClientId = dotenv.env['GOOGLE_IOS_CLIENT_ID'];

final GoogleSignIn googleSignIn = GoogleSignIn(

// Get the singleton instance.
final GoogleSignIn googleSignIn = GoogleSignIn.instance;

// Initialize the GoogleSignIn instance.
await googleSignIn.initialize(
clientId: Platform.isIOS ? iosClientId : null,
serverClientId: webClientId,
scopes: ['email', 'profile'],
);

final GoogleSignInAccount? googleUser = await googleSignIn.signIn();
if (googleUser == null) throw 'Sign in cancelled';
// Define scopes (they were moved from the constructor/initialize to the sign-in call).
const List<String> scopes = ['email', 'profile'];

final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
// Sign-in logic
final GoogleSignInAccount? googleUser = await googleSignIn.authenticate(
scopeHint: scopes,
);
if (googleUser == null) throw 'Sign in cancelled';
Comment on lines +100 to +116
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

🧩 Analysis chain

Verify the Google Sign-In v7 authentication flow.

The migration to google_sign_in v7 uses authenticate() with scopeHint, but this pattern should be verified against the official migration guide. In v7.0+, the typical flow is:

  • signIn() was replaced by authenticate() for initial sign-in
  • authorizeScopes() is typically used for incremental authorization (requesting additional scopes after initial sign-in)

The current implementation calls both authenticate() and then immediately authorizeScopes(), which may be redundant. Please verify:

  1. Whether authenticate() already returns tokens that can be accessed directly
  2. If the scopeHint parameter is the correct API for passing scopes to authenticate()
  3. Whether authorizeScopes() is necessary immediately after authenticate() for the initial sign-in flow

🌐 Web query:

google_sign_in Flutter package version 7.0 migration guide authenticate method

πŸ’‘ Result:

Summary β€” what changed in google_sign_in v7.x

  • signIn() was replaced by authenticate(); signInSilently() β†’ attemptLightweightAuthentication(). Authentication (who the user is) is separate from authorization (OAuth scopes / access tokens). [1][2][3]

Quick migration steps (minimal)

  1. Initialize once:
    await GoogleSignIn.instance.initialize();. [1]

  2. Lightweight / silent:
    await GoogleSignIn.instance.attemptLightweightAuthentication(); // no UI if possible. [1]

  3. User-initiated sign-in UI:
    final GoogleSignInAccount account = await GoogleSignIn.instance.authenticate();
    // may throw GoogleSignInException if cancelled. [1][4]

  4. Request OAuth tokens (authorization) only after authentication:
    // request scopes / tokens from the account’s authorization client
    final authorized = await account.authorizationClient.authorizeScopes(desiredScopes);
    final accessToken = authorized.accessToken;
    // Use authorized.idToken / serverAuthCode depending on platform and flow. [1][2]

  5. Firebase / signInWithCredential:

    • Use idToken / accessToken from the authorization step to create credentials for Firebase (e.g. GoogleAuthProvider.credential). Ensure you request appropriate token (idToken for Firebase auth). Manage server codes/tokens on your backend when using serverAuthCode. [1][5]

Platform notes / gotchas

  • On web the plugin removed the old signIn behavior; web requires using the Google Identity Services button or following web-specific flow and may not return idToken from authenticate β€” follow web migration docs. [3][6]
  • On platforms where authorizationRequiresUserInteraction() is true, token requests must be started from a user interaction (button press). [1]
  • authenticate() can throw GoogleSignInException (e.g., canceled) instead of returning null; handle exceptions. [4]

Minimal example

  • Authenticate then authorize scopes:
    final account = await GoogleSignIn.instance.authenticate();
    final authorized = await account.authorizationClient.authorizeScopes(['email','profile','https://www.googleapis.com/auth/drive.readonly']);
    final accessToken = authorized.accessToken;
    final idToken = authorized.idToken; // if provided for that flow
    // Use tokens (e.g., Firebase credential, API calls).

Sources
[1] google_sign_in v7.0.0 package docs (migration + API examples).
[2] StackOverflow β€” example showing authenticate() then account.authorizationClient.authorizeScopes(...) to obtain accessToken.
[3] GitHub / flutter issues & plugin notes about signIn removal and web migration.


🌐 Web query:

google_sign_in Flutter 7.0 authorizeScopes vs authenticate difference

πŸ’‘ Result:

Short answer: in google_sign_in v7 the flows are separated β€” authenticate() (or signIn/signInSilently) performs authentication (who the user is) while authorizeScopes / authorizeServer perform OAuth authorization (granting scopes and returning an access token or server auth code). Use authenticate() to sign the user in and get profile/id info; call authorizeScopes(...) (or authorizeServer(...)) when you need accessToken or additional OAuth scopes (Drive, Gmail, backend server code exchange). [1][2][3]

Quick usage sketch:

  • Sign-in (authenticate): GoogleSignIn.instance.authenticate(...) β†’ returns GoogleSignInAccount (identity, idToken on some platforms).
  • Authorize scopes: account.authorizationClient.authorizeScopes(scopes) β†’ returns authorization object with accessToken (or server auth code via authorizeServer). [1][3]

Notes:

  • On web GIS limits token lifetime and the plugin no longer auto-refreshes access tokens β€” you must re-request scopes when tokens expire. [1][2]

Sources:

  • google_sign_in 7.0.0 README/changelog. [1]
  • google_sign_in_web / package docs (scopes, GIS behavior on web). [2]
  • Community explanation / example showing authenticate then authorizeScopes (StackOverflow). [3]

Fix Google Sign-In v7 authentication flow to follow the official migration pattern.

In google_sign_in v7, authentication and authorization are separated: authenticate() performs authentication (identity), while authorizeScopes() performs OAuth authorization (scopes/tokens). The current implementation has two issues:

  1. Incorrect scopeHint parameter: The authenticate() method should not receive scopes; the correct flow is to call authenticate() and then call account.authorizationClient.authorizeScopes(scopes) to obtain OAuth tokens.

  2. Missing authorization step: After authentication, you must call account.authorizationClient.authorizeScopes(desiredScopes) to request OAuth tokens and scopes. This is required for accessing tokens, not redundant.

Required fix: Remove scopeHint from authenticate() and add the authorization step:

final GoogleSignInAccount? googleUser = await googleSignIn.authenticate();
if (googleUser == null) throw 'Sign in cancelled';
final authorized = await googleUser.authorizationClient.authorizeScopes(['email', 'profile']);
final accessToken = authorized.accessToken;
πŸ€– Prompt for AI Agents
In patient/lib/provider/auth_provider.dart around lines 100-116, the current
sign-in calls authenticate() with a scopeHint and never requests OAuth scopes;
remove the scopeHint parameter from googleSignIn.authenticate(), keep the
null-check, then call
googleUser.authorizationClient.authorizeScopes(['email','profile']) to obtain
the OAuth authorization result and extract the access token (e.g.,
authorized.accessToken) for downstream use.


if (googleAuth.idToken == null) throw 'No ID Token found';
if (googleAuth.accessToken == null) throw 'No Access Token found';
// Authorization to get access token
final authorization = await googleUser.authorizationClient.authorizeScopes(
scopes
);
final String accessToken = authorization.accessToken;

// Authentication (It now synchronous as per v7.0+).
final GoogleSignInAuthentication googleAuth = googleUser.authentication;
final String? idToken = googleAuth.idToken;
if (idToken == null) throw 'No ID Token found';

// Supabase login (now with the retrieved accessToken)
await supabase.auth.signInWithIdToken(
provider: OAuthProvider.google,
idToken: googleAuth.idToken!,
accessToken: googleAuth.accessToken,
idToken: idToken,
accessToken: accessToken,
);
}
Comment on lines 95 to 135
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion | 🟠 Major

Significant code duplication with therapist authentication.

The _handleMobileSignIn() method is duplicated in therapist/lib/repository/supabase_auth_repository.dart with identical logic (lines 76-116). Consider extracting this into a shared authentication utility to maintain DRY principles and ensure consistent behavior across both apps.

Create a shared authentication helper:

// In a shared package or common directory
class GoogleSignInHelper {
  static Future<({String idToken, String accessToken})> signInMobile({
    required String webClientId,
    String? iosClientId,
  }) async {
    final GoogleSignIn googleSignIn = GoogleSignIn.instance;
    
    await googleSignIn.initialize(
      clientId: Platform.isIOS ? iosClientId : null,
      serverClientId: webClientId,
    );
    
    const List<String> scopes = ['email', 'profile'];
    
    final GoogleSignInAccount? googleUser = await googleSignIn.authenticate(
      scopeHint: scopes,
    );
    if (googleUser == null) throw Exception('Sign in cancelled');
    
    final authorization = await googleUser.authorizationClient.authorizeScopes(scopes);
    final String accessToken = authorization.accessToken;
    
    final GoogleSignInAuthentication googleAuth = googleUser.authentication;
    final String? idToken = googleAuth.idToken;
    if (idToken == null) throw Exception('No ID Token found');
    
    return (idToken: idToken, accessToken: accessToken);
  }
}

Then use it in both files:

Future<void> _handleMobileSignIn() async {
  final webClientId = dotenv.env['GOOGLE_WEB_CLIENT_ID'] ?? 
    (throw Exception("WEB_CLIENT_ID not found in .env"));
  final iosClientId = dotenv.env['GOOGLE_IOS_CLIENT_ID'];
  
  final tokens = await GoogleSignInHelper.signInMobile(
    webClientId: webClientId,
    iosClientId: iosClientId,
  );
  
  await supabase.auth.signInWithIdToken(
    provider: OAuthProvider.google,
    idToken: tokens.idToken,
    accessToken: tokens.accessToken,
  );
}


Expand Down
12 changes: 6 additions & 6 deletions patient/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ dependencies:
cupertino_icons: ^1.0.6
google_fonts: ^6.1.0
flutter_native_splash: ^2.3.6
google_sign_in: ^6.1.6
carousel_slider: ^4.0.0
flutter_dotenv: ^5.2.1
google_sign_in: ^7.2.0
carousel_slider: ^5.1.1
flutter_dotenv: ^6.0.0


# For information on the generic Dart part of this file, see the
Expand All @@ -71,18 +71,18 @@ dependencies:
intl: ^0.20.2
flutter_svg: ^2.0.17
vector_graphics: ^1.1.18
dotted_border: ^2.1.0
dotted_border: ^3.1.0
uuid: ^4.5.1
flutter_gemini: ^3.0.0
get_it: ^8.2.0
get_it: ^9.0.5
rxdart: ^0.28.0

dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.4.15
dart_mappable_builder: ^4.4.0
flutter_lints: ^3.0.0
flutter_lints: ^6.0.0
flutter_gen_runner:

# The following line ensures that the Material Icons font is
Expand Down
2 changes: 1 addition & 1 deletion therapist/android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ class TherapyDottedEmptyContainer extends StatelessWidget {
return GestureDetector(
onTap: () => _addFromExistingTherapyDetails(context),
child: DottedBorder(
color: const Color(0xffC5C3C3),
radius: const Radius.circular(16),
options: RoundedRectDottedBorderOptions(
// color and radius now go inside options
radius: const Radius.circular(16),
color: const Color(0xffC5C3C3),
),
child: Container(
alignment: Alignment.center,
width: double.infinity,
Expand Down
39 changes: 27 additions & 12 deletions therapist/lib/repository/supabase_auth_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,29 +74,44 @@ class SupabaseAuthRepository implements AuthRepository {
}

Future<void> _handleMobileSignIn() async {
final webClientId = dotenv.env['GOOGLE_WEB_CLIENT_ID'] ??
(throw Exception("WEB_CLIENT_ID not found in .env"));
final webClientId = dotenv.env['GOOGLE_WEB_CLIENT_ID'] ??
(throw Exception("WEB_CLIENT_ID not found in .env"));
final iosClientId = dotenv.env['GOOGLE_IOS_CLIENT_ID'];

final GoogleSignIn googleSignIn = GoogleSignIn(

// Get the singleton instance.
final GoogleSignIn googleSignIn = GoogleSignIn.instance;

// Initialize the GoogleSignIn instance.
await googleSignIn.initialize(
clientId: Platform.isIOS ? iosClientId : null,
serverClientId: webClientId,
scopes: ['email', 'profile'],
);

final GoogleSignInAccount? googleUser = await googleSignIn.signIn();
// Define scopes (they were moved from the constructor/initialize to the sign-in call).
const List<String> scopes = ['email', 'profile'];

// Sign-in logic
final GoogleSignInAccount? googleUser = await googleSignIn.authenticate(
scopeHint: scopes,
);
if (googleUser == null) throw 'Sign in cancelled';

final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
// Authorization to get access token
final authorization = await googleUser.authorizationClient.authorizeScopes(
scopes
);
final String accessToken = authorization.accessToken;

if (googleAuth.idToken == null) throw 'No ID Token found';
if (googleAuth.accessToken == null) throw 'No Access Token found';
// Authentication (It now synchronous as per v7.0+).
final GoogleSignInAuthentication googleAuth = googleUser.authentication;
final String? idToken = googleAuth.idToken;
if (idToken == null) throw 'No ID Token found';

// Supabase login (now with the retrieved accessToken)
await _supabaseClient.auth.signInWithIdToken(
provider: OAuthProvider.google,
idToken: googleAuth.idToken!,
accessToken: googleAuth.accessToken,
idToken: idToken,
accessToken: accessToken,
);
}
Comment on lines 76 to 116
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Same Google Sign-In v7 concerns as patient app.

This mobile sign-in implementation is identical to patient/lib/provider/auth_provider.dart (lines 95-135). Please refer to the review comments on that file regarding:

  1. Verification of the authenticate() + authorizeScopes() pattern
  2. OAuth scope format validation
  3. Typed exceptions instead of string literals
  4. Code duplication across patient and therapist apps

The suggestion to extract shared authentication logic into a common utility would eliminate this duplication and ensure consistent behavior.

See the detailed refactor suggestion in the review of patient/lib/provider/auth_provider.dart (lines 95-135).


🏁 Script executed:

#!/bin/bash
# Locate and display the patient app auth provider file
echo "=== Checking patient app auth file ==="
fd "auth_provider.dart" --type f 2>/dev/null | head -20

echo -e "\n=== Displaying patient/lib/provider/auth_provider.dart (lines 95-135) ==="
if [ -f "patient/lib/provider/auth_provider.dart" ]; then
  sed -n '95,135p' "patient/lib/provider/auth_provider.dart"
else
  echo "File not found at patient/lib/provider/auth_provider.dart"
fi

echo -e "\n=== Searching for other sign-in implementations ==="
rg -n "authenticate\s*\(" --type dart -B2 -A2 | head -50

Length of output: 2510


🏁 Script executed:

#!/bin/bash
# Search for OAuth/GoogleSignIn patterns to identify code duplication
echo "=== Searching for GoogleSignIn.authenticate patterns ==="
rg -n "GoogleSignIn.*authenticate|googleSignIn\.authenticate" --type dart -B3 -A3

echo -e "\n=== Searching for authorizeScopes patterns ==="
rg -n "authorizeScopes" --type dart -B2 -A2

echo -e "\n=== Searching for shared auth utilities ==="
fd "auth" --type d | grep -i util
fd "auth" --type f -path "*/shared/*" -o -path "*/common/*" | head -20

Length of output: 2319


🏁 Script executed:

#!/bin/bash
# Check exception handling patterns
echo "=== String exceptions in sign-in methods ==="
rg -n "throw\s+['\"].*['\"]" --type dart -B2 -A1 | grep -i "sign\|auth" | head -30

echo -e "\n=== Comparing file structures ==="
echo "Therapist file:"
wc -l therapist/lib/repository/supabase_auth_repository.dart 2>/dev/null || echo "Not found"

echo -e "\nPatient file:"
wc -l patient/lib/provider/auth_provider.dart 2>/dev/null || echo "Not found"

Length of output: 1575


Extract shared Google Sign-In v7 authentication logic into a common utility to eliminate duplication and standardize exception handling.

Code duplication confirmed: both patient (patient/lib/provider/auth_provider.dart lines 95-135) and therapist (therapist/lib/repository/supabase_auth_repository.dart lines 76-116) apps contain identical _handleMobileSignIn() implementations. Additionally, both use string exceptions ('Sign in cancelled', 'No ID Token found') instead of typed exceptionsβ€”replace with Exception or custom exception classes. Extract the shared logic into a reusable utility (e.g., lib/shared/auth/google_sign_in_handler.dart) to maintain consistent behavior across apps.

πŸ€– Prompt for AI Agents
In therapist/lib/repository/supabase_auth_repository.dart around lines 76-116,
the mobile Google Sign-In logic is duplicated and throws string exceptions;
extract this logic into a shared utility (e.g.,
lib/shared/auth/google_sign_in_handler.dart) that: reads client IDs from dotenv,
initializes GoogleSignIn with platform-specific clientId and serverClientId,
requests scopes, performs the sign-in and authorization, returns a typed result
(e.g., object with idToken and accessToken) and throws typed Exceptions (or a
small custom exception class) instead of string literals; then replace the
in-file implementation with a call to the new utility and update imports in both
therapist and patient apps to use it so behavior and error handling are
centralized and consistent.


Expand Down
18 changes: 9 additions & 9 deletions therapist/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ environment:
dependencies:
flutter:
sdk: flutter
easy_date_timeline: ^1.0.4
easy_date_timeline: ^2.0.9

# State Management
provider: ^6.1.2
Expand All @@ -42,25 +42,25 @@ dependencies:
dart_mappable: ^4.4.0
google_fonts: ^6.1.0
flutter_native_splash: ^2.3.6
google_sign_in: ^6.1.6
google_sign_in: ^7.2.0
flutter_svg: ^2.0.9
smooth_page_indicator: ^1.1.0

# intl
intl: ^0.19.0
intl: ^0.20.2

# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.6
flutter_dotenv: ^5.2.1
dotted_border: ^2.1.0
flutter_dotenv: ^6.0.0
dotted_border: ^3.1.0
uuid: ^4.5.1
flutter_gemini: ^3.0.0
get_it: ^8.2.0
get_it: ^9.0.5

# Voice features
speech_to_text: ^6.6.0
flutter_tts: ^3.8.5
speech_to_text: ^7.3.0
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

🧩 Analysis chain

Excellent: speech_to_text upgraded beyond target version.

The issue #142 targeted speech_to_text ^6.x.x, but this PR upgrades to ^7.3.0, which is even better as it includes more recent bug fixes and improvements. Ensure the upgrade doesn't introduce breaking changes in the speech recognition functionality.


🌐 Web query:

speech_to_text Flutter package 7.0 breaking changes migration

πŸ’‘ Result:

Here are the key breaking / migration points for speech_to_text >= 7.0 and what to change (short checklist + minimal code examples).

Summary of changes

  • v7.0: MacOS support + WASM web compilation; removed support for deprecated Android v1 plugin registration; updated Android build settings; improved locale / on‑device language handling. [1][2]
  • v6.6.0 (important precursor): listen(...) now prefers a single SpeechListenOptions parameter (several older listen parameters were deprecated). [2][3]

What to do when upgrading to 7.x

  1. Update Android embedding to V2 (remove legacy v1 registration)

    • The plugin no longer supports the old (pre-Flutter‑1.12) v1 registration. Ensure your Android app uses the V2 embedding (MainActivity extends FlutterActivity and does not manually register plugins with PluginRegistry). If you have custom plugin registration code, remove/convert it to the V2 approach. (See the package changelog note about removing deprecated v1 registration.) [1]
  2. Replace deprecated listen parameters with SpeechListenOptions

    • Prior code like:
      await speech.listen(
      onResult: _onResult,
      listenFor: Duration(seconds:30),
      localeId: 'en_US',
      partialResults: true,
      cancelOnError: false,
      ...
      );
    • Should be migrated to:
      await speech.listen(
      onResult: _onResult,
      listenOptions: SpeechListenOptions(
      listenFor: Duration(seconds:30),
      localeId: 'en_US',
      partialResults: true,
      cancelOnError: false,
      onDevice: false, // example field
      autoPunctuation: true, // example field
      enableHapticFeedback: true // example field
      ),
      );
    • Use the example and API docs to see all available fields on SpeechListenOptions. [2][3]
  3. Check async behavior of stop/cancel (older breaking change)

    • Earlier releases made stop() and cancel() async. If you awaited stop/cancel previously or relied on synchronous behavior, be sure to await them now. (This change predates 7.0 but may affect your code.) [1]
  4. Android build updates / language codes

    • Update Android build settings if you see deprecation/build warnings (changelog notes updates). Also test on-device recognition with language codes β€” v7.0 adjusted handling for on‑device recognition and locale duplicates were improved. Verify locales() and localeId usage in your app. [1]
  5. macOS and web (WASM)

    • If you target macOS, add macOS platform support in your project (enable macOS in Flutter if not already). For web, 7.x adds WASM compilation support β€” test web builds for any JS/wasm dependency changes. [1]
  6. Run & test on all target platforms

    • Test initialize(), locales(), listen(), stop(), cancel() on each platform you target (Android, iOS, web, macOS). Look for differences in pause timeouts and partialResult behavior (Android may impose its own pause timeout).

Useful references (read these while migrating)

  • Changelog (7.0 notes + earlier): pub.dev speech_to_text changelog. [1]
  • API docs & usage (SpeechListenOptions / listen examples): package docs & example on pub.dev. [2][3]
  • Repository (examples / issues if you hit platform-specific bugs): GitHub csdcorp/speech_to_text. [4]

References
[1] speech_to_text changelog (7.0 release notes).
[2] speech_to_text API docs / documentation (SpeechListenOptions & listen).
[3] speech_to_text example (example usage showing SpeechListenOptions).
[4] csdcorp/speech_to_text GitHub repo.

If you want I can:

  • scan your pubspec + a small code snippet and produce the exact edits (MainActivity, listen calls) needed.

I need to verify how speech_to_text is actually used in the codebase to determine if the breaking changes in v7.0 require code updates.


🏁 Script executed:

#!/bin/bash
# Search for speech_to_text imports and usage in the therapist app
rg -n "speech_to_text|SpeechToText" --type dart -C 3

Length of output: 994


🏁 Script executed:

#!/bin/bash
# Search specifically for listen() calls which may need migration
rg -nP "\.listen\s*\(" --type dart -C 5

Length of output: 4576


🏁 Script executed:

#!/bin/bash
# Check for SpeechListenOptions (v7.0 new parameter structure)
rg "SpeechListenOptions" --type dart

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check Android embedding version in MainActivity
fd "MainActivity" --type f -x cat {}

Length of output: 261


🏁 Script executed:

#!/bin/bash
# Check Android build configuration
fd "build.gradle" --type f | xargs cat

Length of output: 4554


🏁 Script executed:

#!/bin/bash
# Look for any explicit platform configurations
fd "pubspec.yaml" -x cat {}

Length of output: 8820


🏁 Script executed:

#!/bin/bash
# Check if there are any deprecated listen() parameter usages
rg -nP "listen\s*\(\s*onResult|listen\s*\(\s*listenFor|listen\s*\(\s*localeId" --type dart

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for any web or macOS configuration files
fd -e "web" -e "macos" --type d -C 5

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Check for iOS and Android platform configurations
ls -la ios/ android/ macos/ web/ 2>/dev/null || echo "Checking available directories..."

Length of output: 33


🏁 Script executed:

#!/bin/bash
# Get an overview of the project structure
find . -maxdepth 2 -type d -name "lib" -o -name "android" -o -name "ios" -o -name "macos" -o -name "web" | head -20

Length of output: 161


🏁 Script executed:

#!/bin/bash
# Check for migration-related comments or TODOs in the code
rg -n "TODO|FIXME|migrate|breaking" --type dart -i | head -20

Length of output: 1001


🏁 Script executed:

#!/bin/bash
# Get full pubspec.yaml to understand all dependencies and platform support
cat therapist/pubspec.yaml

Length of output: 4452


🏁 Script executed:

#!/bin/bash
# Check if speech_to_text is actually used by searching pubspec files
rg "speech_to_text" --type yaml

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Look for platform-specific implementations
find . -path "*/lib/*" -name "*.dart" -type f | xargs rg -l "SpeechToText\|speech_to_text" 2>/dev/null | head -10

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check for any listen method calls in detail
rg -nPA 15 "\.listen\(" --type dart | head -50

Length of output: 4189


🏁 Script executed:

#!/bin/bash
# Verify Android embedding by checking MainActivity
find . -name "MainActivity.kt" -o -name "MainActivity.java" | xargs cat 2>/dev/null

Length of output: 261


🏁 Script executed:

#!/bin/bash
# Check gradle version and settings
find . -path "*/android/*" -name "*.gradle" | xargs head -30

Length of output: 4119


🏁 Script executed:

#!/bin/bash
# Look for migration issues or platform-specific code
rg -n "onDevice|autoPunctuation|enableHapticFeedback|listenOptions" --type dart

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if macOS is supported
test -d "macos" && echo "macOS platform exists" || echo "macOS platform not found"
test -d "web" && echo "web platform exists" || echo "web platform not found"

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Final comprehensive check - list all Dart files that might use speech_to_text
find therapist/lib -name "*.dart" -type f 2>/dev/null | xargs grep -l "speech_to_text\|SpeechToText" 2>/dev/null

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Check the actual listen() implementation for any parameter passing
rg -nA 20 "await.*\.listen\(" --type dart

Length of output: 1679


🏁 Script executed:

#!/bin/bash
# Search for stop() and cancel() calls
rg -nP "\.stop\(\)|\.cancel\(\)" --type dart -C 2

Length of output: 3199


🏁 Script executed:

#!/bin/bash
# Check for initialize() calls which might need verification
rg -nP "\.initialize\(" --type dart -C 3

Length of output: 3641


🏁 Script executed:

#!/bin/bash
# Look for locale-related code
rg -n "localeId|locales\(\)" --type dart -C 2

Length of output: 440


🏁 Script executed:

#!/bin/bash
# Examine version constraints in pubspec
cat therapist/pubspec.yaml | grep -A 2 -B 2 "speech_to_text"

Length of output: 69


🏁 Script executed:

#!/bin/bash
# Final check - see if there's any existing migration discussion or PRs
git log --oneline --all | grep -i "speech\|upgrade\|migrate\|7\." | head -10

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Check git diff to see exactly what changed
git diff HEAD -- therapist/pubspec.yaml

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Verify the full context of the change
git show HEAD:therapist/pubspec.yaml | grep -A 1 -B 1 "speech_to_text" || echo "Previous version check failed"

Length of output: 65


🏁 Script executed:

#!/bin/bash
# Look for any error handling or try-catch around speech recognition
rg -n "try\s*{|catch|Exception" --type dart -C 3 | grep -A 5 -B 5 "listen\|speech"

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Check if this is an existing project with established usage
test -f "therapist/pubspec.lock" && echo "Lock file exists" && head -50 therapist/pubspec.lock | grep -A 2 "speech_to_text"

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Simple check - does the code actually import and use speech_to_text?
rg -c "speech_to_text" therapist/lib/ therapist/pubspec.yaml 2>/dev/null || echo "No usage found"

Length of output: 73


🏁 Script executed:

#!/bin/bash
# List all files in lib directory
ls -la therapist/lib/ 2>/dev/null || ls -la lib/ 2>/dev/null || find . -maxdepth 3 -type d -name "lib"

Length of output: 466


🏁 Script executed:

#!/bin/bash
# Search broadly for any speech recognition code
rg -n "speech|recognize|listen" --type dart -i | head -30

Length of output: 2175


Fix required: Migrate speech_to_text listen() call to v7.0+ API

The upgrade introduces a breaking change in the listen() method signature. v6.6.0+ deprecated separate listen parameters in favor of a single SpeechListenOptions parameter.

File: therapist/lib/core/services/voice_service.dart, lines 85–98

Replace the current listen() call with the new structure:

await _speechToText.listen(
  onResult: (result) {
    if (result.finalResult) {
      _speechController.add(result.recognizedWords);
    }
  },
  listenOptions: SpeechListenOptions(
    listenFor: const Duration(seconds: 30),
    pauseFor: const Duration(seconds: 3),
    partialResults: true,
    localeId: "en_US",
    onSoundLevelChange: (level) {
      // Optional: Handle sound level changes
    },
  ),
);

Also verify the Android app uses V2 embedding (MainActivity extends FlutterActivity)β€”this is already correct in your setup.

πŸ€– Prompt for AI Agents
In therapist/lib/core/services/voice_service.dart around lines 85 to 98, the
current speech_to_text.listen() call uses the old parameter style; update it to
the v7+ API by calling _speechToText.listen with onResult as a named callback
and pass a single listenOptions: SpeechListenOptions(...) containing listenFor,
pauseFor, partialResults, localeId and optional onSoundLevelChange; ensure
inside onResult you check result.finalResult and add result.recognizedWords to
_speechController, and remove the old separate parameters so the call matches
the example structure from the review comment.

flutter_tts: ^4.2.3

dev_dependencies:
flutter_test:
Expand All @@ -73,7 +73,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^3.0.0
flutter_lints: ^6.0.0

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
Expand Down