jsonflutterthemedata

Why does my Flutter app hang at the splash screen when using ThemeDecoder in package:json_theme_plus to import a JSON theme generated by Appainter?


I am trying to implement a global theme in a Flutter app that has been generated by Appainter as a JSON file. I have followed the instructions to implement the JSON file here. When I start debugging, the app hangs at the splash screen. Using a breakpoint and stepover, I can see that the code is hanging at the line:

  final theme = ThemeDecoder.decodeThemeData(themeJson)!;

The JSON file I am importing was created using Appainter using the default colour scheme, i.e. I haven't made any changes to it.

I have put the JSON code into a JSON syntax checker and this reports that the JSON has no issues.

I should note that the app was running fine until I made this change.

Here is the code for my main dart file. I have commented the line where it hangs.

import 'dart:convert'; // For jsonDecode
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // For rootBundle
import 'package:get_it/get_it.dart';
import 'package:json_theme_plus/json_theme_plus.dart';
import 'package:responsive_sizer/responsive_sizer.dart';
import 'package:frequent/audio/audio_player_helper.dart';
import 'package:frequent/database/page_data/chart_data_helper.dart';
import 'package:frequent/database/page_data/frequencies_helper.dart';
import 'package:frequent/database/page_data/global_theme.dart';
import 'package:frequent/database/page_data/instruments_helper.dart';
import 'package:frequent/database/page_data/page_data_helper.dart';
import 'package:frequent/database/page_data/ranges_helper.dart';
import 'package:frequent/database/page_data/settings_helper.dart';
import 'package:frequent/database/page_data/statistics_helper.dart';
import 'package:frequent/database/tables&fields/data_fn_frequencies_helper.dart';
import 'package:frequent/database/tables&fields/data_fn_instruments_helper.dart';
import 'package:frequent/database/tables&fields/data_fn_ranges_helper.dart';
import 'package:frequent/database/tables&fields/data_fn_settings_helper.dart';
import 'package:frequent/database/tables&fields/data_fn_statistics_helper.dart';
import 'package:frequent/functions/populate_sort.dart';
import 'package:frequent/maintain/backup/maintain_backup.dart';
import 'package:frequent/maintain/maintain_options.dart';
import 'package:frequent/my_home_page.dart';
import 'package:frequent/revise/revise_home_w.dart';
import 'package:frequent/about/about_home_w.dart';
import 'package:frequent/database/tables&fields/db_database.dart';
import 'package:frequent/learn/flashcards/cards&pages/learn_flashcards_w.dart';
import 'package:frequent/learn/flashcards/cards&pages/learn_results_w.dart';
import 'package:frequent/learn/learn_home_w.dart';
import 'package:frequent/maintain/list_view/maintain_list_w.dart';
import 'package:frequent/maintain/maintain_home_w.dart';
import 'package:frequent/revise/revise_list_w.dart';
import 'package:frequent/statistics/instrument_report.dart';
import 'package:frequent/statistics/stats_home_w.dart';

void main() async {
  debugPrint('before WidgetsFlutterBindings');
  WidgetsFlutterBinding.ensureInitialized();
  debugPrint('before await RootBudnle');
  final themeStr = await rootBundle.loadString('assets/appainter_theme.json');
  debugPrint('before jsonDecodeThemeData');
  final themeJson = jsonDecode(themeStr);
  debugPrint('before ThemeDecoder');
  final theme = ThemeDecoder.decodeThemeData(themeJson)!; // APP HANGS HERE
  debugPrint('before runApp');
  runApp(MyApp(theme: theme));
}

class MyApp extends StatefulWidget {
  MyApp({super.key, required this.theme}) {
    database = MyDatabase();
    //
    // Register PageData singleton in GetIt
    GetIt.instance.registerSingleton<PageDataHelper>(PageDataHelper());
    //
    // Add initial PageData values for MyApp
    GetIt.instance<PageDataHelper>()
        .add(page: MyApp, key: MyApp.dbName, object: database);
    snackBarKey = GlobalKey<ScaffoldMessengerState>();
    GetIt.instance<PageDataHelper>()
        .add(page: MyApp, key: MyApp.snackBar, object: snackBarKey);
  }
  // Theme variable for JSON Theme created by Appainter
  final ThemeData theme;
  //
  // Debug flag for various debug features
  static bool debug = true; // Hard-coded, should be 'false' for release
  //
  // Version and copyright year values
  static String version = '0.93.46';
  static String copyrightYear = "2023";
  //
  // Sizes of on-screen display items
  static double defaultBtnHeightP = 4.5.h;
  static double defaultBtnWidthP = 30.0.w;
  static double defaultBtnFontP = 17.sp;
  static double defaultBtnHeightL = 10.0.h;
  static double defaultBtnWidthL = 13.0.w;
  static double defaultBtnFontL = 18.sp;
  //
  // Names for PageData variable storage
  static String dbName = 'myAppDatabase';
  static String snackBar = 'myAppSnackBar';
  //
  // Global variables - database and snackbar
  late final MyDatabase database;
  late final GlobalKey<ScaffoldMessengerState> snackBarKey;

  @override
  State<MyApp> createState() => MyAppState();
}

class MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    //
    // Register singleton helpers
    GetIt.instance.registerSingleton<FrequenciesHelper>(FrequenciesHelper());
    GetIt.instance.registerSingleton<InstrumentsHelper>(InstrumentsHelper());
    GetIt.instance.registerSingleton<AudioPlayerHelper>(AudioPlayerHelper());
    GetIt.instance.registerSingleton<ChartDataHelper>(ChartDataHelper());
    GetIt.instance.registerSingleton<RangesHelper>(RangesHelper());
    GetIt.instance.registerSingleton<SettingsHelper>(SettingsHelper());
    GetIt.instance.registerSingleton<StatisticsHelper>(StatisticsHelper());
    GetIt.instance
        .registerSingleton<DataFrequenciesHelper>(DataFrequenciesHelper());
    GetIt.instance
        .registerSingleton<DataInstrumentsHelper>(DataInstrumentsHelper());
    GetIt.instance.registerSingleton<DataRangesHelper>(DataRangesHelper());
    GetIt.instance.registerSingleton<DataSettingsHelper>(DataSettingsHelper());
    GetIt.instance
        .registerSingleton<DataStatisticsHelper>(DataStatisticsHelper());
    //
    // Set up the application settings
    GetIt.instance<SettingsHelper>().init(db: widget.database);
    //
    // Setup custom sort function for MyHomePage
    GetIt.instance.registerSingleton<PopulateSort>(PopulateSort());
  }

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return ResponsiveSizer(builder: (context, orientation, screenType) {
      GetIt.instance<PageDataHelper>()
          .add(page: MyHomePage, key: MyHomePage.title, object: 'Frequent');
      return AnimatedBuilder(
          animation: GetIt.instance<SettingsHelper>().themeListenable(),
          builder: (context, child) {
            return MaterialApp(
              scaffoldMessengerKey: widget.snackBarKey,
              title: 'Frequency Study Aid',
              theme: widget.theme,
              //darkTheme: GlobalThemData.darkThemeData,
              themeMode: GetIt.instance<SettingsHelper>().themeMode(),
              home: MyHomePage(),
              routes: {
                '/home': (context) => MyHomePage(),
                '/maintainhome': (context) => MaintainHome(),
                '/maintainfrequency': (context) => MaintainList<Frequency>(),
                '/maintaininstrument': (context) => MaintainList<Instrument>(),
                '/maintainrange': (context) => MaintainList<Rangename>(),
                '/maintainoptions': (context) => MaintainOptions(),
                '/maintainbackup': (context) => MaintainBackup(),
                // This is not a route, but included here to show there is a page
                // with this name
                // '/inputform': (context) => InputForm(),
                '/revisehome': (context) => ReviseHome(),
                '/reviselist': (context) => ReviseList(),
                // This is not a route, but included here to show there is a page
                // with this name
                // '/revisechart': (context) => ReviseChart(),
                '/learn': (context) => LearnHome(),
                '/learnflash': (context) => LearnFlashcards(),
                '/learnresults': (context) => LearnResults(),
                '/statistics': (context) => StatsHome(),
                '/instrumentreport': (context) => InstrumentReport(),
                '/about': (context) => AboutHome(),
              },
            );
          });
    });
  }
}

This is the debug console output from the session:

Launching lib\main.dart on Pixel 7 in debug mode...
√ Built build\app\outputs\flutter-apk\app-debug.apk
Connecting to VM Service at ws://127.0.0.1:51143/rgXMilecEos=/ws
Connected to the VM Service.
I/flutter (13272): before WidgetsFlutterBindings
I/flutter (13272): before await RootBudnle
I/flutter (13272): before jsonDecodeThemeData
I/flutter (13272): before ThemeDecoder
D/ProfileInstaller(13272): Installing profile for com.example.frequent

At this point, the app is hung at the splash screen on my phone and the console doesn't progress any further. As I noted earlier, if I insert a breakpoint at the first line of code and step through, it hangs at the line "final theme = ThemeDecoder.decodeThemeData(themeJson)!;"

This app uses json_theme_plus 6.6.5, as prescribed in the installation instructions.
I also changed over to the Flutter package json_theme 6.5.4, which appears to be updated to support the latest version of Flutter. This package hangs at the same line of code.

If I change the offending line as follows: final theme = ThemeDecoder.decodeThemeData(themeJson, validate: false)!; // APP HANGS HERE

then the output from the console looks like this (i.e. it progresses beyond the ThemeDecoder line), but the app is still stuck at the splash screen.

Launching lib\main.dart on Pixel 7 in debug mode...
√ Built build\app\outputs\flutter-apk\app-debug.apk
Connecting to VM Service at ws://127.0.0.1:53678/Vu-AXUAj4no=/ws
Connected to the VM Service.
I/flutter (18487): before WidgetsFlutterBindings
I/flutter (18487): before await RootBudnle
I/flutter (18487): before jsonDecodeThemeData
I/flutter (18487): before ThemeDecoder
I/flutter (18487): before runApp
D/ProfileInstaller(18487): Installing profile for com.example.frequent
I/xample.frequent(18487): Background concurrent mark compact GC freed 19MB AllocSpace bytes, 3(60KB) LOS objects, 90% free, 2578KB/26MB, paused 228us,5.894ms total 36.587ms

Here is my pubspec.yaml file:

description: A new Flutter project.

environment:
  sdk: ^3.5.4

dependencies:
  flutter:
    sdk: flutter
  audiofileplayer: ^2.1.1
  cupertino_icons: ^1.0.2
  drift: ^2.4.2
  external_path: ^2.0.1
  ffi: ^2.0.1
  file_picker: ^8.0.2
  flip_card: ^0.7.0
  flutter_colorpicker: ^1.0.3
  get_it: ^8.0.2
  intl: ^0.20.0
  json_theme_plus: ^6.6.5
  lecle_downloads_path_provider: ^0.0.2+8
  mockito: ^5.4.2
  open_file_plus: ^3.3.0
  path: ^1.8.2
  path_provider: ^2.0.11
  permission_handler: ^11.3.1
  responsive_sizer: ^3.1.1
  sqlite3_flutter_libs: ^0.5.10
  toggle_switch: ^2.0.1

   
dev_dependencies:
  flutter_test:
    sdk: flutter
  drift_dev: ^2.2.0+1
  build_runner: ^2.2.1

  flutter_lints: ^4.0.0

flutter:

  uses-material-design: true

  assets:
    - assets/appainter_theme.json

  fonts:
    - family: Pacifico
      fonts:
        - asset: assets/fonts/Pacifico-Regular.ttf
    - family: Satisfy
      fonts:
        - asset: assets/fonts/Satisfy-Regular.ttf

Here is the output from flutter doctor -v:

[√] Flutter (Channel stable, 3.24.5, on Microsoft Windows [Version 10.0.22631.4541], locale en-GB)
    • Flutter version 3.24.5 on channel stable at D:\flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision dec2ee5c1f (3 weeks ago), 2024-11-13 11:13:06 -0800
    • Engine revision a18df97ca5
    • Dart version 3.5.4
    • DevTools version 2.37.3

[√] Windows Version (Installed version of Windows is version 10 or higher)

[√] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
    • Android SDK at D:\Users\LaptopBob\AppData\Local\Android\Sdk
    • Platform android-34, build-tools 35.0.0
    • ANDROID_HOME = D:\Users\LaptopBob\AppData\Local\Android\Sdk
    • Java binary at: C:\Program Files\Java\jdk-19.0.1\bin\java
    • Java version OpenJDK Runtime Environment (build 19.0.1+10-21)
    • All Android licenses accepted.

[√] Chrome - develop for the web
    • Chrome at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe

[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.12.1)
    • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community
    • Visual Studio Community 2022 version 17.12.35514.174
    • Windows 10 SDK version 10.0.22621.0

[√] Android Studio (version 2024.2)
    • Android Studio at C:\Users\LaptopBob\AppData\Local\Programs\Android Studio
    • Flutter plugin can be installed from:
       https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
       https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 21.0.3+-12282718-b509.11)

[√] VS Code (version 1.95.3)
    • VS Code at C:\Users\LaptopBob\AppData\Local\Programs\Microsoft VS Code
    • Flutter extension version 3.102.0

[√] Connected device (4 available)
    • Pixel 7 (mobile)  • 192.168.1.176:38307 • android-arm64  • Android 15 (API 35)
    • Windows (desktop) • windows             • windows-x64    • Microsoft Windows [Version 10.0.22631.4541]
    • Chrome (web)      • chrome              • web-javascript • Google Chrome 131.0.6778.86
    • Edge (web)        • edge                • web-javascript • Microsoft Edge 131.0.2903.70
    ! Device emulator-5562 is offline.

[√] Network resources
    • All expected network resources are available.

• No issues found!

I haven't added the JSON file generated by Appainter because it breaks the character limit for a post, but can try to add a download link to this if that would be useful.

Does anyone have any ideas why this is happening?


Solution

  • Having updated to the latest release of json_theme_plus (v6.6.6), my application runs without hanging now.