From a5636c7ab7f1afcd0c379837a79fefab0b6a847a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr>
Date: Wed, 29 Nov 2023 23:15:39 +0100
Subject: [PATCH] Fix data propagation between widgets

---
 android/gradle.properties      |   4 +-
 lib/cubit/data_cubit.dart      |   4 ++
 lib/main.dart                  |  27 +++++---
 lib/ui/screens/demo_page.dart  | 112 ++++++++++++++++++---------------
 lib/ui/skeleton.dart           |  56 ++++++-----------
 lib/ui/widgets/app_bar.dart    |   2 -
 lib/ui/widgets/header_app.dart |  45 ++++++++-----
 pubspec.yaml                   |   2 +-
 8 files changed, 137 insertions(+), 115 deletions(-)

diff --git a/android/gradle.properties b/android/gradle.properties
index 79b0a52..7aef769 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,5 +1,5 @@
 org.gradle.jvmargs=-Xmx1536M
 android.useAndroidX=true
 android.enableJetifier=true
-app.versionName=1.0.35
-app.versionCode=36
+app.versionName=1.0.36
+app.versionCode=37
diff --git a/lib/cubit/data_cubit.dart b/lib/cubit/data_cubit.dart
index 05335fb..262453a 100644
--- a/lib/cubit/data_cubit.dart
+++ b/lib/cubit/data_cubit.dart
@@ -11,6 +11,10 @@ class DataCubit extends HydratedCubit<DataState> {
     emit(state);
   }
 
+  void updateCounter(int delta) {
+    emit(DataState(counter: (state.counter ?? 0) + delta));
+  }
+
   @override
   DataState? fromJson(Map<String, dynamic> json) {
     int counter = json['counter'] as int;
diff --git a/lib/main.dart b/lib/main.dart
index f05fb0c..1a3b173 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -2,11 +2,15 @@ import 'dart:io';
 
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:hive/hive.dart';
 import 'package:hydrated_bloc/hydrated_bloc.dart';
 import 'package:path_provider/path_provider.dart';
 
 import 'package:random/config/theme.dart';
+import 'package:random/cubit/bottom_nav_cubit.dart';
+import 'package:random/cubit/data_cubit.dart';
+import 'package:random/cubit/settings_cubit.dart';
 import 'package:random/ui/skeleton.dart';
 
 void main() async {
@@ -38,14 +42,21 @@ class MyApp extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return MaterialApp(
-      title: 'Random application',
-      theme: appTheme,
-      home: SkeletonScreen(),
-      localizationsDelegates: context.localizationDelegates,
-      supportedLocales: context.supportedLocales,
-      locale: context.locale,
-      debugShowCheckedModeBanner: false,
+    return MultiBlocProvider(
+      providers: [
+        BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()),
+        BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()),
+        BlocProvider<DataCubit>(create: (context) => DataCubit()),
+      ],
+      child: MaterialApp(
+        title: 'Random application',
+        theme: appTheme,
+        home: SkeletonScreen(),
+        localizationsDelegates: context.localizationDelegates,
+        supportedLocales: context.supportedLocales,
+        locale: context.locale,
+        debugShowCheckedModeBanner: false,
+      ),
     );
   }
 }
diff --git a/lib/ui/screens/demo_page.dart b/lib/ui/screens/demo_page.dart
index 03afe0a..8d10142 100644
--- a/lib/ui/screens/demo_page.dart
+++ b/lib/ui/screens/demo_page.dart
@@ -21,7 +21,10 @@ class DemoPage extends StatelessWidget {
           SizedBox(height: 8),
           AppHeader(text: 'TOP'),
           SizedBox(height: 20),
-          persistedCounterBlock(),
+          persistedCounterBlock(BlocProvider.of<DataCubit>(context)),
+          SizedBox(height: 20),
+          testBlocConsumer(),
+          testBlocBuilder(),
           SizedBox(height: 20),
           fakeApiCall(),
           SizedBox(height: 20),
@@ -31,61 +34,68 @@ class DemoPage extends StatelessWidget {
     );
   }
 
-  Widget persistedCounterBlock() {
-    return BlocProvider<DataCubit>(
-      create: (BuildContext context) => DataCubit(),
-      child: BlocBuilder<DataCubit, DataState>(
-        builder: (BuildContext context, DataState state) {
-          void updateCounter(int delta) {
-            BlocProvider.of<DataCubit>(context).getData(
-              DataState(counter: (state.counter ?? 0) + delta),
-            );
-          }
-
-          return Row(
-            mainAxisAlignment: MainAxisAlignment.center,
-            crossAxisAlignment: CrossAxisAlignment.center,
-            children: [
-              IconButton(
-                icon: Icon(UniconsSolid.arrow_circle_down),
-                color: appTheme.primaryColor,
-                onPressed: () => updateCounter(-1),
-              ),
-              Padding(
-                padding: EdgeInsets.all(10),
-                child: Text(state.counter.toString()),
-              ),
-              IconButton(
-                icon: Icon(UniconsSolid.arrow_circle_up),
-                color: appTheme.primaryColor,
-                onPressed: () => updateCounter(1),
-              ),
-            ],
-          );
-        },
-      ),
+  Widget persistedCounterBlock(DataCubit dataCubit) {
+    return Row(
+      mainAxisAlignment: MainAxisAlignment.center,
+      crossAxisAlignment: CrossAxisAlignment.center,
+      children: [
+        IconButton(
+          icon: Icon(UniconsSolid.arrow_circle_down),
+          color: appTheme.primaryColor,
+          onPressed: () => dataCubit.updateCounter(-1),
+        ),
+        testBlocConsumer(),
+        IconButton(
+          icon: Icon(UniconsSolid.arrow_circle_up),
+          color: appTheme.primaryColor,
+          onPressed: () => dataCubit.updateCounter(1),
+        ),
+      ],
     );
   }
 
   Widget fakeApiCall() {
-    return BlocProvider<SettingsCubit>(
-      create: (BuildContext context) => SettingsCubit(),
-      child: BlocBuilder<SettingsCubit, SettingsState>(
-        builder: (BuildContext context, SettingsState state) {
-          SettingsCubit settings = BlocProvider.of<SettingsCubit>(context);
+    return BlocBuilder<SettingsCubit, SettingsState>(
+      builder: (context, settingsSate) {
+        return Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          crossAxisAlignment: CrossAxisAlignment.center,
+          children: [
+            Text('apiUrl: ' + settingsSate.apiUrl.toString()),
+            Text('securityToken: ' + (settingsSate.securityToken.toString())),
+            Text('interfaceType: ' + settingsSate.interfaceType.toString()),
+          ],
+        );
+      },
+    );
+  }
 
-          return Column(
-            mainAxisAlignment: MainAxisAlignment.center,
-            crossAxisAlignment: CrossAxisAlignment.center,
-            children: [
-              Text('apiUrl: ' + settings.getApiUrl()),
-              Text('securityToken: ' + settings.getSecurityToken()),
-              Text('interfaceType: ' + settings.getInterfaceType().toString()),
-              Text('unknown: ' + settings.getSetting('unknown', 'undefined').toString()),
-            ],
-          );
-        },
-      ),
+  Widget testBlocConsumer() {
+    return BlocConsumer<DataCubit, DataState>(
+      listener: (context, dataState) {
+        // do stuff here based on state
+      },
+      builder: (context, dataState) {
+        // return widget here based on state
+        return Text('BlocConsumer / ' + dataState.toString());
+      },
+    );
+  }
+
+  Widget testBlocListener() {
+    return BlocListener<DataCubit, DataState>(
+      listener: (context, dataState) {
+        // do stuff here based on state
+      },
+    );
+  }
+
+  Widget testBlocBuilder() {
+    return BlocBuilder<DataCubit, DataState>(
+      builder: (context, dataState) {
+        // return widget here based on state
+        return Text('BlocBuilder / ' + dataState.toString());
+      },
     );
   }
 }
diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart
index 0f01672..6492f6a 100644
--- a/lib/ui/skeleton.dart
+++ b/lib/ui/skeleton.dart
@@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_swipe/flutter_swipe.dart';
 
 import 'package:random/cubit/bottom_nav_cubit.dart';
-import 'package:random/cubit/settings_cubit.dart';
 import 'package:random/ui/screens/about_page.dart';
 import 'package:random/ui/screens/demo_page.dart';
 import 'package:random/ui/screens/graph_page.dart';
@@ -21,8 +20,6 @@ class SkeletonScreen extends StatefulWidget {
 class _SkeletonScreenState extends State<SkeletonScreen> {
   @override
   Widget build(BuildContext context) {
-    print('SkeletonScreen - build');
-
     const List<Widget> pageNavigation = <Widget>[
       DemoPage(),
       GraphPage(),
@@ -30,41 +27,28 @@ class _SkeletonScreenState extends State<SkeletonScreen> {
       AboutPage(),
     ];
 
-    return BlocProvider<SettingsCubit>(
-      create: (BuildContext context) => SettingsCubit(),
-      child: BlocProvider<BottomNavCubit>(
-        create: (BuildContext context) => BottomNavCubit(),
-        child: BlocBuilder<BottomNavCubit, int>(
-          builder: (BuildContext context, int state) {
-            return Scaffold(
-              extendBodyBehindAppBar: false,
-              appBar: StandardAppBar(),
-              body: Swiper(
-                itemCount: BlocProvider.of<BottomNavCubit>(context).pagesCount,
-                itemBuilder: (BuildContext context, int index) {
-                  return AnimatedSwitcher(
-                    duration: const Duration(milliseconds: 300),
-                    child: pageNavigation.elementAt(index),
-                  );
-                },
-                pagination: SwiperPagination(
-                  builder: SwiperCustomPagination(
-                    builder: (BuildContext context, SwiperPluginConfig config) {
-                      return BottomNavBar(swipeController: config.controller);
-                    },
-                  ),
-                ),
-                onIndexChanged: (newPageIndex) {
-                  BlocProvider.of<BottomNavCubit>(context).updateIndex(newPageIndex);
-                },
-                outer: true,
-                loop: false,
-              ),
-              backgroundColor: Theme.of(context).colorScheme.background,
-            );
-          },
+    return Scaffold(
+      extendBodyBehindAppBar: false,
+      appBar: StandardAppBar(),
+      body: Swiper(
+        itemCount: BlocProvider.of<BottomNavCubit>(context).pagesCount,
+        itemBuilder: (BuildContext context, int index) {
+          return pageNavigation.elementAt(index);
+        },
+        pagination: SwiperPagination(
+          builder: SwiperCustomPagination(
+            builder: (BuildContext context, SwiperPluginConfig config) {
+              return BottomNavBar(swipeController: config.controller);
+            },
+          ),
         ),
+        onIndexChanged: (newPageIndex) {
+          BlocProvider.of<BottomNavCubit>(context).updateIndex(newPageIndex);
+        },
+        outer: true,
+        loop: false,
       ),
+      backgroundColor: Theme.of(context).colorScheme.background,
     );
   }
 }
diff --git a/lib/ui/widgets/app_bar.dart b/lib/ui/widgets/app_bar.dart
index 3afc4bb..648a641 100644
--- a/lib/ui/widgets/app_bar.dart
+++ b/lib/ui/widgets/app_bar.dart
@@ -7,8 +7,6 @@ class StandardAppBar extends StatelessWidget implements PreferredSizeWidget {
 
   @override
   Widget build(BuildContext context) {
-    print('StandardAppBar - build');
-
     return AppBar(
       title: AppHeader(text: 'app_name'),
       actions: [
diff --git a/lib/ui/widgets/header_app.dart b/lib/ui/widgets/header_app.dart
index a64053e..af80150 100644
--- a/lib/ui/widgets/header_app.dart
+++ b/lib/ui/widgets/header_app.dart
@@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
+import 'package:random/cubit/data_cubit.dart';
 import 'package:random/cubit/settings_cubit.dart';
 import 'package:random/models/interface_type.dart';
 
@@ -12,24 +13,38 @@ class AppHeader extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    print('AppHeader - build (' + this.text + ')');
+    return Row(
+      mainAxisAlignment: MainAxisAlignment.start,
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        Text(
+          tr(text),
+          textAlign: TextAlign.start,
+          style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2),
+        ),
+        SizedBox(width: 2),
+        expertInterfaceIndicator(),
+        SizedBox(width: 2),
+        dataCounterIndicator(),
+      ],
+    );
+  }
 
-    return BlocProvider<SettingsCubit>(
-      create: (BuildContext context) => SettingsCubit(),
-      child: BlocBuilder<SettingsCubit, SettingsState>(
-        builder: (BuildContext context, SettingsState state) {
-          SettingsCubit settings = BlocProvider.of<SettingsCubit>(context);
+  Widget expertInterfaceIndicator() {
+    return BlocBuilder<SettingsCubit, SettingsState>(
+      builder: (BuildContext context, SettingsState settingsState) {
+        bool isExpert = settingsState.interfaceType == InterfaceType.expert;
 
-          bool isExpert = settings.getInterfaceType() == InterfaceType.expert;
-          String titleSuffix = isExpert ? ' ⭐' : '';
+        return Text(isExpert ? '⭐' : '');
+      },
+    );
+  }
 
-          return Text(
-            tr(text) + titleSuffix,
-            textAlign: TextAlign.start,
-            style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2),
-          );
-        },
-      ),
+  Widget dataCounterIndicator() {
+    return BlocBuilder<DataCubit, DataState>(
+      builder: (context, dataState) {
+        return Text('(' + dataState.counter.toString() + ')');
+      },
     );
   }
 }
diff --git a/pubspec.yaml b/pubspec.yaml
index 7edccdd..4e20996 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@ description: A random application, for testing purpose only.
 
 publish_to: 'none'
 
-version: 1.0.35+36
+version: 1.0.36+37
 
 environment:
   sdk: '^3.0.0'
-- 
GitLab