Skip to content

fix: persist selected language across app restarts#316

Open
bhavjsh wants to merge 1 commit intofossasia:mainfrom
bhavjsh:fix-language-persistence
Open

fix: persist selected language across app restarts#316
bhavjsh wants to merge 1 commit intofossasia:mainfrom
bhavjsh:fix-language-persistence

Conversation

@bhavjsh
Copy link

@bhavjsh bhavjsh commented Jan 5, 2026

Fixes #167

Summary

This PR fixes an issue where the selected language was not retained after restarting the app.
The chosen locale is now persisted locally and restored on app startup, ensuring a consistent
localized experience for users.

What was changed

  • Persist selected locale using SharedPreferences
  • Restore saved locale during app initialization via LocaleProvider

How this was tested

  • Selected a different language from the Settings screen
  • Navigated across multiple screens
  • Restarted the app / refreshed the web build
  • Verified the selected language persists correctly across sessions

Screenshots

(Added below)
Screenshot (1447)
Screenshot (1448)
Screenshot (1449)

Screenshots demonstrate language selection persistence across navigation and app restart.

Summary by Sourcery

Persist the user-selected locale across app restarts and initialize the app with any previously saved language choice.

New Features:

  • Store the selected app locale in local storage so it is remembered between sessions.
  • Provide an option to clear the saved locale and reset to the default language behavior.

Enhancements:

  • Initialize the locale provider on startup by loading any previously saved locale from persistent storage.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 5, 2026

Reviewer's Guide

Persists the user-selected locale using SharedPreferences and ensures it is loaded into LocaleProvider during app startup so language choice survives restarts, while also adding an API to clear the stored locale.

Sequence diagram for locale selection and restoration

sequenceDiagram
  actor User
  participant SettingsScreen
  participant LocaleProvider
  participant SharedPreferences
  participant FlutterApp

  rect rgb(230,230,250)
    User->>SettingsScreen: chooseLanguage(languageCode)
    SettingsScreen->>LocaleProvider: setLocale(Locale(languageCode))
    activate LocaleProvider
    LocaleProvider->>SharedPreferences: getInstance()
    activate SharedPreferences
    SharedPreferences-->>LocaleProvider: prefs
    LocaleProvider->>SharedPreferences: setString(app_locale, languageCode)
    SharedPreferences-->>LocaleProvider: success
    LocaleProvider-->>SettingsScreen: localeUpdated
    LocaleProvider->>FlutterApp: notifyListeners()
    deactivate SharedPreferences
    deactivate LocaleProvider
  end

  rect rgb(224,255,255)
    FlutterApp->>LocaleProvider: createInstance()
    activate LocaleProvider
    LocaleProvider->>LocaleProvider: loadSavedLocale()
    LocaleProvider->>SharedPreferences: getInstance()
    activate SharedPreferences
    SharedPreferences-->>LocaleProvider: prefs
    LocaleProvider->>SharedPreferences: getString(app_locale)
    SharedPreferences-->>LocaleProvider: code or null
    alt saved locale exists
      LocaleProvider->>LocaleProvider: _locale = Locale(code)
      LocaleProvider->>FlutterApp: notifyListeners()
    else no saved locale
      LocaleProvider-->>FlutterApp: keep default Locale(en)
    end
    deactivate SharedPreferences
    deactivate LocaleProvider
  end
Loading

Updated class diagram for LocaleProvider

classDiagram
  class ChangeNotifier {
    <<abstract>>
    +void addListener(Function listener)
    +void removeListener(Function listener)
    +void notifyListeners()
  }

  class LocaleProvider {
    -static const String _localeKey
    -Locale _locale
    +LocaleProvider()
    +Locale get locale()
    +Future<void> loadSavedLocale()
    +Future<void> setLocale(Locale newLocale)
    +Future<void> clearLocale()
  }

  class Locale {
    +String languageCode
    +String? countryCode
    +Locale(String languageCode)
    +Locale(String languageCode, String countryCode)
  }

  class SharedPreferences {
    +static Future<SharedPreferences> getInstance()
    +String? getString(String key)
    +Future<bool> setString(String key, String value)
    +Future<bool> remove(String key)
  }

  LocaleProvider ..|> ChangeNotifier
  LocaleProvider --> Locale : uses
  LocaleProvider --> SharedPreferences : persists locale
Loading

File-Level Changes

Change Details Files
Persist and restore the selected locale via SharedPreferences in LocaleProvider.
  • Introduce a private key constant used to store the locale code in local preferences
  • Add an async initializer that reads the saved language code and updates the provider locale if present, notifying listeners
  • Change the locale setter to be async, saving the language code in preferences before notifying listeners
  • Add an API to clear the stored locale and reset to the default locale, updating local preferences and notifying listeners
lib/provider/locale_provider.dart
Initialize LocaleProvider with the previously saved locale on app startup.
  • Update the ChangeNotifierProvider creation for LocaleProvider to invoke the async locale-loading method immediately after construction
  • Document that the saved locale is loaded on startup for consistent localization
lib/main.dart

Assessment against linked issues

Issue Objective Addressed Explanation
#167 Make the selected language setting persist across app restarts by storing the chosen locale and restoring it on startup.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • Persisting only languageCode in setLocale/loadSavedLocale drops any countryCode or scriptCode; if you support region-specific locales (e.g. en_US vs en_GB), consider storing and restoring the full locale identifier.
  • Initializing LocaleProvider with ..loadSavedLocale() triggers an unawaited async call that may cause a brief flicker from the default 'en' locale to the saved one; if this is noticeable in the UI, consider delaying app build until the locale is loaded or exposing a loading state in the provider.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Persisting only `languageCode` in `setLocale`/`loadSavedLocale` drops any `countryCode` or `scriptCode`; if you support region-specific locales (e.g. `en_US` vs `en_GB`), consider storing and restoring the full locale identifier.
- Initializing `LocaleProvider` with `..loadSavedLocale()` triggers an unawaited async call that may cause a brief flicker from the default `'en'` locale to the saved one; if this is noticeable in the UI, consider delaying app build until the locale is loaded or exposing a loading state in the provider.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@bhavjsh
Copy link
Author

bhavjsh commented Jan 7, 2026

@mariobehling please review this , i have fixed the issue

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses issue #167 by persisting the user-selected app language and restoring it on startup so localization remains consistent across app restarts.

Changes:

  • Added locale persistence via SharedPreferences in LocaleProvider.
  • Restored saved locale during app startup by triggering loadSavedLocale() when creating the provider.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
lib/provider/locale_provider.dart Adds load/save/clear locale logic using SharedPreferences.
lib/main.dart Initializes LocaleProvider by triggering locale load during provider creation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 1 to +2
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

shared_preferences is imported here, but the package isn’t declared in pubspec.yaml dependencies (so this change will fail to compile). Add shared_preferences to pubspec.yaml and run flutter pub get / update lockfile as needed.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +35
_locale = Locale(code);
notifyListeners();
}

/// Sets the application's locale and persists it.
///
/// When the locale is changed, it notifies all listening widgets to rebuild.
void setLocale(Locale newLocale) {
Future<void> setLocale(Locale newLocale) async {
_locale = newLocale;

final prefs = await SharedPreferences.getInstance();
await prefs.setString(_localeKey, newLocale.languageCode);

Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

Locale persistence currently stores only newLocale.languageCode and restores via Locale(code). This loses countryCode/scriptCode and can’t round-trip supported locales like pt_BR, nb_NO, or zh-Hans/Hant (see AppLocalizations.supportedLocales). Persist the full locale (e.g., toLanguageTag() or language+script+country) and parse it back with Locale.fromSubtags(...).

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +25
Future<void> loadSavedLocale() async {
final prefs = await SharedPreferences.getInstance();
final code = prefs.getString(_localeKey);

if (code == null || code.isEmpty) return;

_locale = Locale(code);
notifyListeners();
}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

SharedPreferences.getInstance() / read/write calls can throw (platform/channel issues). Since these methods are called without awaiting at the call site (e.g., startup cascade), an exception could surface as an unhandled async error. Consider wrapping SharedPreferences operations in try/catch and failing gracefully (keep the in-memory locale, optionally log).

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +42
/// Optional: Reset to system/default behavior.
/// Use this if you provide a "System Default" option in UI.
Future<void> clearLocale() async {
_locale = const Locale('en'); // or set to null if your app supports null locale
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

The doc comment says “Reset to system/default behavior”, but the implementation forces English (Locale('en')). Either update the comment to match the behavior, or change the provider/API to support a true system-default mode (e.g., nullable locale passed to MaterialApp.locale).

Suggested change
/// Optional: Reset to system/default behavior.
/// Use this if you provide a "System Default" option in UI.
Future<void> clearLocale() async {
_locale = const Locale('en'); // or set to null if your app supports null locale
/// Reset to the app's default locale (English) and clear any saved override.
/// Use this if you provide a "Use app default" option in the UI.
Future<void> clearLocale() async {
_locale = const Locale('en');

Copilot uses AI. Check for mistakes.
Comment on lines 15 to 24
void main() {
setupLocator();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => ImageLoader()),
ChangeNotifierProvider(create: (_) => ImageLibraryProvider()),
ChangeNotifierProvider(create: (_) => LocaleProvider()),
// Load saved locale on startup
ChangeNotifierProvider(create: (_) => LocaleProvider()..loadSavedLocale()),
],
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

loadSavedLocale() is async but is kicked off without being awaited; this will boot the app in the default locale and then rebuild once the preference loads (visible language “flash” on cold start). If you want the correct locale from the first frame, make main() async, call WidgetsFlutterBinding.ensureInitialized(), and await locale loading before runApp (or gate MaterialApp behind a loading state).

Copilot uses AI. Check for mistakes.
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.

Language Setting not persistent

2 participants