Compare commits

..
2 Commits
Author SHA1 Message Date
Jeroen SchweitzerandClaude Opus 4.5 6b6614f482 fix(auth): prevent AuthNotifier auto-dispose causing theme toggle logout
Build and Push / release (push) Successful in 3s
Build and Push / build (push) Successful in 3m1s
Applied @persistentRiverpod annotation to AuthNotifier so it persists
for app lifetime. Previously, theme changes could trigger AuthProvider
rebuild via auto-dispose, causing AsyncLoading state and auth issues.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 09:50:48 +01:00
Jeroen SchweitzerandClaude Opus 4.5 265ca5959d fix: theme toggle causing auth issues due to auto-dispose
Build and Push / release (push) Successful in 3s
Build and Push / build (push) Successful in 3m0s
- Add @persistentRiverpod annotation for providers that need keepAlive
- ThemeProvider now persists for app lifetime
- Refactored API clients to use @persistentRiverpod
- Documented in ARCHITECTURE.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 21:44:03 +01:00
7 changed files with 77 additions and 11 deletions
+19
View File
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.1.15] - 2026-01-05
### Fixed
- Theme toggle causing auth issues due to AuthNotifier auto-dispose
- Applied `@persistentRiverpod` annotation to AuthNotifier
- AuthProvider now persists for app lifetime, preventing rebuild on theme change
## [1.1.14] - 2026-01-04
### Fixed
- Theme toggle causing auth issues due to ThemeProvider auto-dispose
- Added `@persistentRiverpod` annotation for providers that need keepAlive
- ThemeProvider now persists for app lifetime
### Added
- `@persistentRiverpod` annotation in `core/providers/annotations.dart`
- Reusable annotation for providers that should not auto-dispose
- Documented in ARCHITECTURE.md
## [1.1.13] - 2026-01-04
### Added
+35
View File
@@ -420,6 +420,41 @@ ContainerRepository containerRepository(Ref ref) {
}
```
### Persistent Providers
By default, `@riverpod` generates providers with `isAutoDispose: true`, meaning they dispose when no longer watched. This causes issues for:
- **API clients** with interceptors that store a `Ref`
- **App-level state** like theme, auth, config
- **Providers with listeners** to other providers
Use `@persistentRiverpod` from `core/providers/annotations.dart` for these cases:
```dart
import 'package:tatlock_ui/core/providers/annotations.dart';
// ✅ Correct - persists for app lifetime
@persistentRiverpod
Dio coreApiClient(Ref ref) { ... }
@persistentRiverpod
class ThemeNotifier extends _$ThemeNotifier { ... }
// ❌ Wrong - auto-dispose can invalidate stored Ref
@riverpod
Dio coreApiClient(Ref ref) { ... }
```
**When to use `@persistentRiverpod`:**
| Use Case | Annotation | Example |
|----------|------------|---------|
| API clients with interceptors | `@persistentRiverpod` | `coreApiClient`, `tatlockApiClient` |
| Auth state provider | `@persistentRiverpod` | `AuthNotifier` |
| Theme/config providers | `@persistentRiverpod` | `ThemeNotifier` |
| Feature data providers | `@riverpod` (default) | `ContainersNotifier` |
| UI state providers | `@riverpod` (default) | `SearchFilterNotifier` |
## File Naming Conventions
| Type | Convention | Example |
+3 -8
View File
@@ -2,6 +2,7 @@ import 'package:dio/dio.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:tatlock_ui/core/api/api_interceptors.dart';
import 'package:tatlock_ui/core/config/app_config.dart';
import 'package:tatlock_ui/core/providers/annotations.dart';
import 'api_client_native.dart' if (dart.library.html) 'api_client_web.dart'
as platform;
@@ -9,10 +10,7 @@ import 'api_client_native.dart' if (dart.library.html) 'api_client_web.dart'
part 'api_client.g.dart';
/// Provides the Dio instance for Core API.
///
/// Uses keepAlive to prevent auto-dispose - the AuthInterceptor stores
/// a Ref that must remain valid for the lifetime of API requests.
@Riverpod(keepAlive: true)
@persistentRiverpod
Dio coreApiClient(Ref ref) {
final options = BaseOptions(
baseUrl: AppConfig.coreApiUrl,
@@ -36,10 +34,7 @@ Dio coreApiClient(Ref ref) {
}
/// Provides the Dio instance for Tatlock API.
///
/// Uses keepAlive to prevent auto-dispose - the AuthInterceptor stores
/// a Ref that must remain valid for the lifetime of API requests.
@Riverpod(keepAlive: true)
@persistentRiverpod
Dio tatlockApiClient(Ref ref) {
final options = BaseOptions(
baseUrl: AppConfig.tatlockApiUrl,
+2 -1
View File
@@ -6,6 +6,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../config/app_config.dart';
import '../providers/annotations.dart';
import 'auth_datasource.dart';
import 'auth_state.dart';
import 'oidc_service.dart';
@@ -24,7 +25,7 @@ part 'auth_provider.g.dart';
///
/// After OIDC authentication, syncs with core-api via POST /auth/sync
/// to get user profile, roles, and preferences.
@riverpod
@persistentRiverpod
class AuthNotifier extends _$AuthNotifier {
// Storage keys
static const _accessTokenKey = 'auth_access_token';
+15
View File
@@ -0,0 +1,15 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
/// Riverpod annotation for providers that should persist for the app lifetime.
///
/// Use this instead of `@riverpod` when:
/// - The provider holds app-level state (theme, auth, config)
/// - The provider stores a Ref that must remain valid (API clients with interceptors)
/// - Disposing would cause flickering or re-initialization issues
///
/// Example:
/// ```dart
/// @persistentRiverpod
/// class ThemeNotifier extends _$ThemeNotifier { ... }
/// ```
const persistentRiverpod = Riverpod(keepAlive: true);
+2 -1
View File
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tatlock_ui/core/auth/auth_provider.dart';
import 'package:tatlock_ui/core/providers/annotations.dart';
part 'theme_provider.g.dart';
@@ -23,7 +24,7 @@ enum ThemeSetting {
///
/// Syncs with API preferences when user is authenticated. On login, the theme
/// from API preferences takes precedence over local storage.
@riverpod
@persistentRiverpod
class ThemeNotifier extends _$ThemeNotifier {
static const _prefsKey = 'theme_setting';
+1 -1
View File
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.1.13+1
version: 1.1.15+1
environment:
sdk: ^3.10.4