Skip to content

Upgrade flutter_riverpod to 3.x and adopt @riverpod codegen per ADR-0092#4573

Merged
hoangdat merged 11 commits into
masterfrom
feat/riverpod-3-upgrade
Jun 10, 2026
Merged

Upgrade flutter_riverpod to 3.x and adopt @riverpod codegen per ADR-0092#4573
hoangdat merged 11 commits into
masterfrom
feat/riverpod-3-upgrade

Conversation

@dab246

@dab246 dab246 commented Jun 1, 2026

Copy link
Copy Markdown
Member

Description

Implements ADR-0092.

Related

Dependency changes

Package Before After Notes
flutter_riverpod ^2.6.1 ^3.3.1 Resolves to 3.3.1
riverpod_annotation ^4.0.2 Stable, runtime only
riverpod_generator 4.0.4-dev.3 Dev dep, build-time only
freezed_annotation transitive ^2.4.4 override ^3.0.0 Annotation-only, safe to override

Code changes

Commit What changed
main.dart Wrap GetMaterialApp with UncontrolledProviderScope(container: appProviderContainer) — shares container between widget tree and GetX layer
LocalSettingsNotifier StateNotifier + manual StateNotifierProvider@Riverpod(keepAlive: true) Notifier. Provider name localSettingsProvider unchanged; 4 call sites unaffected
Composer cache provider chain Delete composer_cache_providers.dart. Move each provider inline to its class file with @Riverpod(keepAlive: true) and ref.watch
ComposerAutoSaveNotifier StateNotifier + StateNotifierProvider.family@Riverpod(keepAlive: true) Notifier family. mounted replaced by _mounted via ref.onDispose. Interactors resolved as late final in build()
Tests ComposerAutoSaveNotifier tests switch from direct instantiation to ProviderContainer with overrides

Composer cache provider chain (after)

File Provider Depends on
composer_hive_cache_client.dart composerHiveCacheClientProvider
cache_exception_thrower.dart cacheExceptionThrowerProvider
composer_persistent_cache_datasource_impl.dart composerCacheDatasourceProvider composerHiveCacheClientProvider, cacheExceptionThrowerProvider
composer_cache_repository_impl.dart composerCacheRepositoryProvider composerCacheDatasourceProvider
resolve_composer_cache_for_restore_interactor.dart resolveComposerCacheForRestoreProvider composerCacheRepositoryProvider
remove_all_composer_cache_interactor.dart removeAllComposerCacheProvider composerCacheRepositoryProvider
mark_composer_cache_clean_close_interactor.dart markComposerLocalCacheCleanCloseProvider composerCacheRepositoryProvider

What is NOT in this PR

appProviderContainer is frozen — no new call sites. The 6 existing call sites below remain and will be removed in future ADRs as each controller migrates to ConsumerWidget.

File Operation
LocalSettingsService .read(localSettingsProvider.notifier).update()
PreferencesController .read(localSettingsProvider.notifier).update()
ThreadController .read(localSettingsProvider) + .listen(...)
SearchEmailController .read(localSettingsProvider)
HandleMobileAutoSaveExtension .read(composerAutoSaveProvider(id).notifier) + .invalidate(...)
HandleComposerRestoreOnMobileExtension .read(resolveComposerCacheForRestoreProvider)

Summary by CodeRabbit

Release Notes

  • Refactor

    • Modernized app state management architecture to use annotation-driven provider patterns for improved maintainability and scalability.
    • Reorganized composer cache and local settings management for better code organization.
  • Chores

    • Updated Riverpod dependencies to latest stable versions.
    • Integrated Riverpod at the application level for enhanced provider management.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR upgrades Riverpod to v3.3.1 and migrates the codebase from manual provider declarations to code-generated @Riverpod annotations. The app now wraps GetMaterialApp with UncontrolledProviderScope to expose Riverpod context. Cache infrastructure providers (Hive client, datasource, repository, exception thrower) are refactored to @Riverpod(keepAlive: true) in two new provider modules. ComposerAutoSaveNotifier and LocalSettingsNotifier are converted from constructor-injected StateNotifier to code-generated notifiers with per-instance/per-app initialization. All consumers—controllers, services, and extensions—are updated to read from the new provider locations and signatures.

Possibly related PRs

  • linagora/tmail-flutter#4495: Introduces MarkComposerCacheCleanCloseInteractor class and related composer-cache restore flow that this PR wraps with @riverpod interactor providers.
  • linagora/tmail-flutter#4496: Adds Android auto-save/clean-close behavior in handle_mobile_auto_save_extension.dart; this PR updates that extension to use the new composer_cache_interactor_providers.dart for markComposerLocalCacheCleanCloseProvider.
  • linagora/tmail-flutter#4364: Threads collapseThreads into search interactor calls; this PR updates SearchEmailController._isCollapseThreadsEnabled to read from localSettingsProvider instead of the prior location.

Suggested reviewers

  • hoangdat
  • tddang-linagora
  • 9clg6
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective: upgrading flutter_riverpod to 3.x and adopting @riverpod codegen, which aligns with the substantial code changes across multiple files implementing ADR-0092.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/riverpod-3-upgrade

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.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
lib/features/composer/presentation/providers/composer_auto_save_notifier.dart (1)

50-62: 💤 Low value

Optional: Use ref.mounted and drop the manual _mounted flag

With flutter_riverpod: ^3.3.1 (Riverpod 3.x), ref.mounted flips to false when the provider is disposed, so _mounted + ref.onDispose(() => _mounted = false) is redundant. Non-blocking—current implementation is correct.

♻️ Proposed simplification
 class ComposerAutoSaveNotifier extends _$ComposerAutoSaveNotifier {
-  bool _mounted = true;
   late final ResolveComposerCacheForRestoreInteractor _resolveInteractor;
   late final RemoveAllComposerCacheInteractor _removeInteractor;

   `@override`
   ComposerAutoSaveState build(String composerId) {
     _resolveInteractor = ref.read(resolveComposerCacheForRestoreProvider);
     _removeInteractor = ref.read(removeAllComposerCacheProvider);
-    ref.onDispose(() => _mounted = false);
     return const ComposerAutoSaveState();
   }

-  bool get mounted => _mounted;
+  bool get mounted => ref.mounted;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@lib/features/composer/presentation/providers/composer_auto_save_notifier.dart`
around lines 50 - 62, The manual _mounted flag and ref.onDispose cleanup in
ComposerAutoSaveNotifier.build should be removed and replaced by using
ref.mounted; delete the _mounted field and its ref.onDispose call, update the
mounted getter to return ref.mounted, and keep _resolveInteractor and
_removeInteractor initialization inside build (referencing build, _mounted,
ref.onDispose, and the mounted getter to locate the code).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@lib/features/composer/presentation/providers/composer_auto_save_notifier.dart`:
- Around line 50-62: The manual _mounted flag and ref.onDispose cleanup in
ComposerAutoSaveNotifier.build should be removed and replaced by using
ref.mounted; delete the _mounted field and its ref.onDispose call, update the
mounted getter to return ref.mounted, and keep _resolveInteractor and
_removeInteractor initialization inside build (referencing build, _mounted,
ref.onDispose, and the mounted getter to locate the code).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6f8eba3c-53fe-42fe-9dbc-43c59ff93dd7

📥 Commits

Reviewing files that changed from the base of the PR and between e43f7d9 and 5072f51.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • lib/features/caching/clients/composer_hive_cache_client.dart
  • lib/features/composer/presentation/extensions/handle_mobile_auto_save_extension.dart
  • lib/features/composer/presentation/providers/composer_auto_save_notifier.dart
  • lib/features/composer/presentation/providers/composer_cache_providers.dart
  • lib/features/mailbox_dashboard/data/datasource_impl/composer_persistent_cache_datasource_impl.dart
  • lib/features/mailbox_dashboard/data/repository/composer_cache_repository_impl.dart
  • lib/features/mailbox_dashboard/domain/usecases/mark_composer_cache_clean_close_interactor.dart
  • lib/features/mailbox_dashboard/domain/usecases/remove_all_composer_cache_interactor.dart
  • lib/features/mailbox_dashboard/domain/usecases/resolve_composer_cache_for_restore_interactor.dart
  • lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart
  • lib/features/mailbox_dashboard/presentation/extensions/handle_composer_restore_on_mobile_extension.dart
  • lib/features/manage_account/presentation/preferences/preferences_controller.dart
  • lib/features/manage_account/presentation/providers/local_settings_notifier.dart
  • lib/features/manage_account/presentation/services/local_settings_service.dart
  • lib/features/search/email/presentation/search_email_controller.dart
  • lib/features/thread/presentation/thread_controller.dart
  • lib/main.dart
  • lib/main/exceptions/thrower/cache_exception_thrower.dart
  • lib/main/providers/app_provider_container.dart
  • pubspec.yaml
  • test/features/composer/presentation/providers/composer_auto_save_notifier_test.dart
💤 Files with no reviewable changes (1)
  • lib/features/composer/presentation/providers/composer_cache_providers.dart

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

This PR has been deployed to https://linagora.github.io/tmail-flutter/4573.

Comment thread pubspec.yaml
codescene-delta-analysis[bot]

This comment was marked as outdated.

@dab246 dab246 force-pushed the feat/riverpod-3-upgrade branch from a52bf22 to 6558afe Compare June 2, 2026 04:52
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/features/composer/presentation/providers/composer_auto_save_notifier.dart (1)

79-110: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard state mutations that occur after await with the mounted check.

Both restore and clearCache set state after an await. Per the doc comment (Lines 46–47), this provider is explicitly invalidated from ComposerController.onClose. If invalidation happens while one of these async calls is in flight, the post-await state = ... will throw a StateError because the notifier has been disposed. The new mounted getter exists but isn't used to guard these mutations — a regression from the previously _mounted-guarded StateNotifier.

🛡️ Proposed fix to guard state after async gaps
       (success) {
         if (success is! ResolveComposerCacheForRestoreSuccess) return null;
         final cache = success.cache;
-        if (cache != null) {
+        if (cache != null && mounted) {
           state = state.copyWith(hasRecoverableSnapshot: true);
           log('ComposerAutoSaveNotifier::restore: found snapshot');
         }
         return cache;
       },
       (failure) => log('ComposerAutoSaveNotifier::clearCache: failure=${failure.runtimeType}'),
       (_) {
-        state = state.copyWith(hasRecoverableSnapshot: false);
-        log('ComposerAutoSaveNotifier::clearCache: cleared');
+        if (mounted) {
+          state = state.copyWith(hasRecoverableSnapshot: false);
+        }
+        log('ComposerAutoSaveNotifier::clearCache: cleared');
       },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@lib/features/composer/presentation/providers/composer_auto_save_notifier.dart`
around lines 79 - 110, Both restore and clearCache mutate state after awaiting
async interactor calls; guard those post-await state assignments with the
notifier's mounted check. Specifically, in restore (method restore) after the
await/result.fold and before any state = ... or log that assumes notifier alive,
check if (!mounted) return null (or return early) before setting state or
returning cache; in clearCache (method clearCache) do the same: after the
await/result.fold return early if not mounted before calling state =
state.copyWith(...). Use the existing mounted getter to avoid StateError when
the provider was invalidated mid-flight.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@lib/features/composer/presentation/providers/composer_auto_save_notifier.dart`:
- Around line 79-110: Both restore and clearCache mutate state after awaiting
async interactor calls; guard those post-await state assignments with the
notifier's mounted check. Specifically, in restore (method restore) after the
await/result.fold and before any state = ... or log that assumes notifier alive,
check if (!mounted) return null (or return early) before setting state or
returning cache; in clearCache (method clearCache) do the same: after the
await/result.fold return early if not mounted before calling state =
state.copyWith(...). Use the existing mounted getter to avoid StateError when
the provider was invalidated mid-flight.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 95187efe-2e69-4af2-8f94-64aa78bf3082

📥 Commits

Reviewing files that changed from the base of the PR and between 5072f51 and a52bf22.

📒 Files selected for processing (2)
  • lib/features/composer/presentation/providers/composer_auto_save_notifier.dart
  • pubspec.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • pubspec.yaml

Comment thread lib/main/exceptions/thrower/cache_exception_thrower.dart Outdated
codescene-delta-analysis[bot]

This comment was marked as outdated.

@dab246 dab246 requested a review from hoangdat June 2, 2026 06:20
@chibenwa chibenwa requested review from 9clg6 and jbrnzl June 2, 2026 06:58

@9clg6 9clg6 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well done !
Some comments but nothing huge

Comment thread lib/features/caching/clients/composer_hive_cache_client.dart Outdated
Comment thread lib/features/composer/presentation/providers/composer_auto_save_notifier.dart Outdated
Comment thread lib/features/composer/presentation/providers/composer_auto_save_notifier.dart Outdated
Comment thread lib/features/composer/presentation/providers/composer_auto_save_notifier.dart Outdated
@chibenwa

chibenwa commented Jun 2, 2026

Copy link
Copy Markdown
Member

Glad to see some consensual changes ;-)

I'm interested in @dab246 @tddang-linagora @hoangdat opinion:

  • does this change brings in your opinion a better code organisation / easier mechanics ? It's ok to say no or I do not know.
  • can it be generalized to other parts of the code base easily? Or will we have difficulties at some point? Or will some GetIT usage be harder to address? IE what's your feedback regarding ease-of-migration ?

jbrnzl
jbrnzl previously approved these changes Jun 2, 2026

@jbrnzl jbrnzl left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Clean! Nice work 🎉

codescene-delta-analysis[bot]

This comment was marked as outdated.

Signed-off-by: dab246 <tdvu@linagora.com>

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Our agent can fix these. Install it.

Gates Passed
3 Quality Gates Passed

Absence of Expected Change Pattern

  • tmail-flutter/lib/features/mailbox_dashboard/data/repository/composer_cache_repository_impl.dart is usually changed with: tmail-flutter/lib/features/mailbox_dashboard/data/datasource_impl/composer_session_cache_datasource_impl.dart, tmail-flutter/lib/features/mailbox_dashboard/domain/repository/composer_cache_repository.dart

Quality Gate Profile: The Bare Minimum
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

@hoangdat

hoangdat commented Jun 3, 2026

Copy link
Copy Markdown
Member

does this change brings in your opinion a better code organisation / easier mechanics ? It's ok to say no or I do not know.

for me it not much more different, but we can move forward to have first step to apply riverpod. With these, we hope we can easily to develop new features with Riverpod than continue to depend on our ghost controller. But tbh, the very big challenge: all our UI depend on GetX (Obx), to completely apply Riverpod, we still have huge work to do.

can it be generalized to other parts of the code base easily? Or will we have difficulties at some point? Or will some GetIT usage be harder to address? IE what's your feedback regarding ease-of-migration ?

We are trying to generalized the way to apply Riverpod. it is in progress, the first function we apply is #4576.

  • Move all the logic of clean all email from controller into riverpod provider
  • Add new logic of clean Trash's sub folder in riverpod also
  • Adapt the centralized error handling to riverpod

We will discover more and more during the way we apply Riverpod, hope we can soon generalize it to other functions

@chibenwa

chibenwa commented Jun 3, 2026

Copy link
Copy Markdown
Member

Thanks for the honest feedback

@hoangdat hoangdat merged commit c846a0d into master Jun 10, 2026
21 checks passed
@hoangdat hoangdat deleted the feat/riverpod-3-upgrade branch June 10, 2026 12:25
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.

6 participants