How to load theme at beginning in Flutter - save

I want to users can change and save the theme color in my app. However, I have no ideas how to load the saved theme color when the app starts running. For example, I want to load the saved theme color directly in the comment place below. I tried SharedPreference. However, the SharedPreference instance needs to run with await. It seems can't be used here. Is there any way I can load the saved theme here directly instead of using setState or something like it?
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: // how to load saved theme here?
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

This answer goes a bit further. It shows how to load and save theme preferences, how to build a ThemeData, and how to change the theme from a page of your app.
Save the user preferences (which theme is selected) using the shared_preferences plugin.
Use the "controller pattern" that is used throughout the Flutter framework to provide the currently selected theme (and changes to it) to your app.
Use an InheritedWidget to use the controller in any part of your app.
Here is how the controller looks like:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// provides the currently selected theme, saves changed theme preferences to disk
class ThemeController extends ChangeNotifier {
static const themePrefKey = 'theme';
ThemeController(this._prefs) {
// load theme from preferences on initialization
_currentTheme = _prefs.getString(themePrefKey) ?? 'light';
}
final SharedPreferences _prefs;
String _currentTheme;
/// get the current theme
String get currentTheme => _currentTheme;
void setTheme(String theme) {
_currentTheme = theme;
// notify the app that the theme was changed
notifyListeners();
// store updated theme on disk
_prefs.setString(themePrefKey, theme);
}
/// get the controller from any page of your app
static ThemeController of(BuildContext context) {
final provider = context.inheritFromWidgetOfExactType(ThemeControllerProvider) as ThemeControllerProvider;
return provider.controller;
}
}
/// provides the theme controller to any page of your app
class ThemeControllerProvider extends InheritedWidget {
const ThemeControllerProvider({Key key, this.controller, Widget child}) : super(key: key, child: child);
final ThemeController controller;
#override
bool updateShouldNotify(ThemeControllerProvider old) => controller != old.controller;
}
Here is how you would use the controller and InheritedWidget in your app:
void main() async {
// load the shared preferences from disk before the app is started
final prefs = await SharedPreferences.getInstance();
// create new theme controller, which will get the currently selected from shared preferences
final themeController = ThemeController(prefs);
runApp(MyApp(themeController: themeController));
}
class MyApp extends StatelessWidget {
final ThemeController themeController;
const MyApp({Key key, this.themeController}) : super(key: key);
#override
Widget build(BuildContext context) {
// use AnimatedBuilder to listen to theme changes (listen to ChangeNotifier)
// the app will be rebuilt when the theme changes
return AnimatedBuilder(
animation: themeController,
builder: (context, _) {
// wrap app in inherited widget to provide the ThemeController to all pages
return ThemeControllerProvider(
controller: themeController,
child: MaterialApp(
title: 'Flutter Demo',
theme: _buildCurrentTheme(),
home: MyHomePage(),
),
);
},
);
}
// build the flutter theme from the saved theme string
ThemeData _buildCurrentTheme() {
switch (themeController.currentTheme) {
case "dark":
return ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.orange,
);
case "light":
default:
return ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.blue,
);
}
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
// thanks to the inherited widget, we can access the theme controller from any page
ThemeController.of(context).setTheme('light');
},
child: Text('Light Theme'),
),
RaisedButton(
onPressed: () {
ThemeController.of(context).setTheme('dark');
},
child: Text('Dark Theme'),
)
],
),
),
);
}
}

You have a few options as to how you'd load it. The first is as Gunter said in a comment - you make MyApp into a stateful widget and load it with initState(), then setState it.
That would look something like this:
class MyApp extends StatefulWidget {
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
ThemeData theme = ThemeData.dark(); // whatever your default is
#override
void initState() {
super.initState();
SharedProperties.getInstance().then((prefs) {
ThemeData theme = ThemeData.light(); // load from prefs here
setState(() => this.theme = theme);
});
}
...
}
The second option is to use a FutureBuilder.
class MyApp extends StatelessWidget {
final Future<ThemeData> loadThemeData = SharedPreferences.getInstance().then((prefs) {
... get theme from prefs
return ThemeData.light();
});
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: loadThemeData,
builder: (context, snapshot) {
return MaterialApp(
theme: snapshot.data,
);
},
initialData: ThemeData.dark(), // whatever you want your default theme to be
);
}
}
The third option is to do the loading before you actually start your app - in your main method. I don't know if this is really recommended as if sharedpreferences takes a while it could delay the start of your app, but realistically it should be very quick and you probably want to avoid a flash different theme showing anyways.
main() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
ThemeData theme = ThemeData.dark(); // get theme from prefs
runApp(MyApp(
theme: theme,
));
}
class MyApp extends StatelessWidget {
final ThemeData theme;
const MyApp({Key key, #required this.theme}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: theme,
....
);
}
}

Load theme data from local storage in main function as await

Related

how can i pass a variable to a class and call that variable in any other screen without it being reset

i want to be able to call an empty variable from a class, assign a value to it and make it persistent, anything aside provider e.t.c would be help, i don't want to overhaul the entire app again to do some bloc, provider e.t.c
NB: all screens are stateful widgets
i have tried creating a class with an empty string and passing a value to it from another screen, but this doesn't seem to work
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
class MethodA {
// id(user, context){
// var name =user.email;
// }
String identity;
MethodA({this.iD});
bool isLoggedIn() {
if (FirebaseAuth.instance.currentUser() != null) {
return true;
} else {
return false;
}
}
Future<void> addUserA( userinfo) async {
//this.iD=id;
Firestore.instance
.collection('user')
.document('furtherinfo').collection(identity).document('Personal Info')
.setData(userdoc)
.catchError((e) {
print(e);
});
}
each time i pass the argument to i.e foo='bar';
and i import that class in another screen, i.e screen 9, foo is automatically set to null, but i would want foo to be bar
I would suggest that you use the Provider since it is the easiest way for me to manage state throughout the app. Flutter starts with one component on top of the widget tree so i would place my provider here.
Example
void main() {runApp(MyApp());}
class MyApp extends StatelessWidget {
MyApp();
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<FirebaseUser>.value(
stream: FirebaseAuth.instance.onAuthStateChanged, // Provider to manage user throughout the app.
),
],
child: MaterialApp(
title: 'My App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.green,
primarySwatch: Colors.green,
accentColor: Colors.yellow,
),
home: MainPage(),
),
);
}
}
Then in your class you can do the following
class MethodAService with ChangeNotifier {
String _identity = null;
FirebaseUser _user = null;
// constructor with the (new changes )
MethodAService(FirebaseUser user){
this._user = user;
}
get identity => _identity ;
setIdentity(String identity) {
_identity = identity ;
notifyListeners(); // required to notify the widgets of your change
}
}
Then when you want to use it anywhere in your app just do the following in the build method
#override
Widget build(BuildContext context) {
final user = Provider.of<FirebaseUser>(context); // to get the current user
final methodA = Provider.of<MethodAService>(context); // get your service with identity
// now you can set the string using
methodA.setIdentity('new identity');
// or just use it like this
if(methodA.identity.isNotEmpty()){
print(methodA.identity);
}else{
print('Identity is empty');
}
return ChangeNotifierProvider<MethodAService>(
builder: (context) => MethodAService(user), // Your provider to manage your object, sending the Firebase user in
child: loggedIn ? HomePage() : LoginPage(), );
}
References
Provider Package
Fireship 185 Provider
Great Youtube video explaining the code
Update for comment
For getting the user uid you can just do user.uid
Changed code above to fit the
I'm not sure put the whole app in a StreamProvider is the best choice. That means the app will be rebuilt on each stream value.
To make a Widget available on all screens, you need a TransitionBuilder in your MaterialApp.
To avoid the external dependency you can also use an InheritedWidget
signed_user.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class SignedUser extends InheritedWidget {
final FirebaseUser user;
SignedUser({#required this.user, #required Widget child})
: super(child: child);
#override
bool updateShouldNotify(SignedUser oldWidget) => true;
static SignedUser of(BuildContext context) =>
context.inheritFromWidgetOfExactType(SignedUser);
}
my_transition_builder.dart
class MyTransitionBuilder extends StatefulWidget {
final Widget child;
const MyTransitionBuilder({Key key, this.child}) : super(key: key);
#override
_MyTransitionBuilderState createState() => _MyTransitionBuilderState();
}
class _MyTransitionBuilderState extends State<MyTransitionBuilder> {
StreamBuilder<FirebaseUser> _builder;
#override
void initState() {
super.initState();
_builder = StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
return SignedUser(
child: widget.child,
user: snapshot.data,
);
});
}
#override
Widget build(BuildContext context) {
return _builder;
}
}
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
// this will make your inherited widget available on all screens of your app
builder: (context, child) {
return MyTransitionBuilder(child: child);
},
routes: {
'/editAccount': (context) => new EditAccountPage(),
},
theme: ThemeData(
primarySwatch: Colors.green,
),
home: MyHomePage(),
);
}
}
usage in edit_account_page.dart
#override
Widget build(BuildContext context) {
var user = SignedUser.of(context).user;
return Scaffold(
body: FutureBuilder<DocumentSnapshot>(
future: Firestore.instance.document('users/${user.uid}').get(),

Flutter Dynamic Theming

What is the best way to go about dynamically changing the theme of a Flutter app? For example, if the user changes the color to red, I want the theme to instantly be changed to red. I can't find anything very helpful online except one guy said to use the BLOC pattern, which I am not familiar with it. I'd like to hear your guys thoughts on the issue. Thanks!
My current code structure:
var themeData = ThemeData(
fontFamily: 'Raleway',
primaryColor: Colors.blue,
brightness: Brightness.light,
backgroundColor: Colors.white,
accentColor: Colors.blue);
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: Constants.appName,
theme: themeData,
home: CheckAuth(), //CheckAuth returns MyHomePage usually
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title, #required this.uid}) : super(key: key);
final String title;
final String uid;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
...build and stuff
}
You can use InhertedWidget if you like (instead of BLOC) - Basically it is used to access parent widget anywhere from the tree.
So what you should do is
create InheritedWidget, somewhere in top of tree [from where you want the effect of theme to take place]
wrap it around Theme widget
expose a method to switch theme, by passing the ThemeData you want to replace it with.
Here is some code:
import 'package:flutter/material.dart';
var themeData = ThemeData(
fontFamily: 'Raleway',
primaryColor: Colors.blue,
brightness: Brightness.light,
backgroundColor: Colors.white,
accentColor: Colors.blue
);
void main() {
runApp(
ThemeSwitcherWidget(
initialTheme: themeData,
child: MyApp(),
),
);
}
class ThemeSwitcher extends InheritedWidget {
final _ThemeSwitcherWidgetState data;
const ThemeSwitcher({
Key key,
#required this.data,
#required Widget child,
}) : assert(child != null),
super(key: key, child: child);
static _ThemeSwitcherWidgetState of(BuildContext context) {
return (context. dependOnInheritedWidgetOfExactType(ThemeSwitcher)
as ThemeSwitcher)
.data;
}
#override
bool updateShouldNotify(ThemeSwitcher old) {
return this != old;
}
}
class ThemeSwitcherWidget extends StatefulWidget {
final ThemeData initialTheme;
final Widget child;
ThemeSwitcherWidget({Key key, this.initialTheme, this.child})
: assert(initialTheme != null),
assert(child != null),
super(key: key);
#override
_ThemeSwitcherWidgetState createState() => _ThemeSwitcherWidgetState();
}
class _ThemeSwitcherWidgetState extends State<ThemeSwitcherWidget> {
ThemeData themeData;
void switchTheme(ThemeData theme) {
setState(() {
themeData = theme;
});
}
#override
Widget build(BuildContext context) {
themeData = themeData ?? widget.initialTheme;
return ThemeSwitcher(
data: this,
child: widget.child,
);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeSwitcher.of(context).themeData,
home: CheckAuth(),
);
}
}
I have wrapped ThemeSwitcherWidget around MaterialApp so the effect is throughout the app (even when you push new route with Navigator).
Use ThemeSwitcher.of(context).switchTheme(themeData) anywhere below ThemeSwithcerWidget to change the theme.
In question's case it should call ThemeSwitcher.of(context).switchTheme(Theme.of(context).copyWith(primaryColor: Colors.red)) to switch primary color to red throught out the app, for eg. on some button click
EDIT: replaced inheritFromWidgetOfExactType -> dependOnInheritedWidgetOfExactType, since it is deprecated - as pointed by Phoca in comments.
Using provider package:
theme_changer.dart
var darkTheme = ThemeData.dark();
var lightTheme= ThemeData.light();
class ThemeChanger extends ChangeNotifier {
ThemeData _themeData;
ThemeChanger(this._themeData);
get getTheme => _themeData;
void setTheme(ThemeData theme) {
_themeData = theme;
notifyListeners();
}
}
main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ThemeChanger(lightTheme)),
],
child: MaterialAppWithTheme(),
);
}
}
class MaterialAppWithTheme extends StatelessWidget {
#override
Widget build(BuildContext context) {
final theme = Provider.of<ThemeChanger>(context);
return MaterialApp(
theme: theme.getTheme,
home: FirstScreen(),
);
}
first_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './theme_changer.dart'
class FirstScreen extends StatelessWidget{
#override
Widget build(BuildContext context){
var _themeProvider=Provider.of<ThemeChanger>(context);
return Scaffold(
appBar: AppBar(title:Text("First Screen"),),
body:Container(width:MediaQuery.of(context).size.width,
height:MediaQuery.of(context).size.height,
child:Center(
child:FlatButton(child:Text("Press me"). onPressed:(){
_themeProvider.setTheme(_themeProvider.getTheme==lightTheme?darkTheme:lightTheme);
})
),
),
);
}
}
This is how to implement the dynamic Theme changing in Your App:
1.You should Change your MyApp into Stateful widget to enable the class to rebuild again when the color changes:
var _primary = Colors.blue ; // This will hold the value of the app main color
var themeData = ThemeData(
fontFamily: 'Raleway',
primaryColor: _primary, // so when the rebuilds the color changes take effect
brightness: Brightness.light,
backgroundColor: Colors.white,
accentColor: Colors.blue);
void main() => runApp(new App());
class App extends StatefulWidget {
App({Key key,}) :
super(key: key);
#override
_AppState createState() => new _AppState();
static void setTheme(BuildContext context, Color newColor) {
_AppState state = context.ancestorStateOfType(TypeMatcher<_AppState>());
state.setState(() {
state._primary = newColor;
});
}
}
2.The static method setTheme will be the one responsible for color changing :
class _AppState extends State<App> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: Constants.appName,
theme: themeData,
home: CheckAuth(), //CheckAuth returns MyHomePage usually
);
}
}
3.When You want to change the theme color from anywhere from your code call this method:
App.setTheme(context, Colors.blue);
You can change the theme using setState or ValueListenableBuilder dynamically without any extension.
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(
title: 'Flutter Basics',
home: StartScreen(),
));
}
class StartScreen extends StatefulWidget {
const StartScreen({Key? key}) : super(key: key);
#override
State<StartScreen> createState() => _StartScreenState();
}
class _StartScreenState extends State<StartScreen> {
#override
Widget build(BuildContext context) {
final notifier = ValueNotifier(ThemeController.type);
return ValueListenableBuilder(
valueListenable: notifier,
builder: (BuildContext context, ThemeType value, Widget? child) {
print(value.name);
return Theme(
data: ThemeController.data,
child: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
setState(() {
int i = (value.index + 1) % ThemeType.values.length;
ThemeController.select(ThemeType.values[i]);
});
},
child: const Text('Change Theme(setState)'),
),
ElevatedButton(
onPressed: () {
int i = (value.index + 1) % ThemeType.values.length;
ThemeController.select(ThemeType.values[i]);
notifier.value = ThemeController.type;
},
child: const Text('Change Theme(Notifier)'),
),
],
),
)),
);
},
);
}
}
enum ThemeType {
dark,
light,
system,
}
class ThemeController {
static ThemeType _type = ThemeType.system;
static ThemeType get type => _type;
static ThemeData _themeData = _getData(ThemeType.system);
static ThemeData get data => _themeData;
static ThemeData select(ThemeType type) {
_type = type;
_themeData = _getData(type);
return _themeData;
}
static ThemeData _getData(ThemeType themeType) {
Brightness brightness = WidgetsBinding.instance.window.platformBrightness;
ThemeType type = themeType == ThemeType.system
? ThemeType.values[brightness.index]
: themeType;
switch (type) {
case ThemeType.dark:
return ThemeData.dark();
case ThemeType.light:
return ThemeData.light();
default:
return _themeData;
}
}
}
An easy approach (to me) is to achieve this is to make use of Stream with InheritedWidget.
The basic idea is to use an InheritedWidget with a StreamController, and wrap your MaterialApp (or a subtree of your app) with a StreamBuilder which gets the Stream from the StreamController from the InheritedWidget.
A complete tested working code sample as follows:
import 'dart:async';
import 'package:flutter/material.dart';
ThemeData darkTheme = ThemeData(
colorSchemeSeed: Colors.amber,
brightness: Brightness.dark,
);
ThemeData lightTheme = ThemeData(
colorSchemeSeed: Colors.blue,
brightness: Brightness.light,
);
void main() {
runApp(
CustomTheme(
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<ThemeData>(
initialData: lightTheme,
stream: CustomTheme.of(context)!.streamController.stream,
builder: (context, snapshot) => MaterialApp(
theme: snapshot.data,
home: const HomeScreen(),
),
);
}
}
class CustomTheme extends InheritedWidget {
CustomTheme({Key? key, required this.child}) : super(key: key, child: child);
final Widget child;
final StreamController<ThemeData> streamController = StreamController();
static CustomTheme? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<CustomTheme>();
}
#override
bool updateShouldNotify(CustomTheme oldWidget) {
return oldWidget != this;
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
CustomTheme customTheme = CustomTheme.of(context)!;
return Scaffold(
appBar: AppBar(
title: const Text('Custom Theme Demo'),
),
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {
customTheme.streamController.add(darkTheme);
},
child: const Text('DARK'),
),
ElevatedButton(
onPressed: () {
customTheme.streamController.add(lightTheme);
},
child: const Text('LIGHT'),
),
],
),
),
);
}
}
I have used get plugin and used Get.changeThemeMode(ThemeMode.(dark/system/light)); it works perfectly for me
First u have to add the get plugin by following the installing guide
then in main change
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Add Your Title',
debugShowCheckedModeBanner: false,
theme:_lightTheme,
darkTheme: _darkTheme,
home: login(),
);
}
}
ON Tap function
import 'package:get/get.dart';
onTap: () {
Get.changeThemeMode(ThemeMode.dark);
setState(() async {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (BuildContext context) => super.widget));
});
}
I have two button for both themes and onTap i have just add the line
Get.changeThemeMode(ThemeMode.dark) for dark mode ,
Get.changeThemeMode(ThemeMode.dark) for light mode

How can I use BottomNavigationBar with BLoC?

When I use BottomNavigationBar with BLoC pattern, it causes the error, Bad state: Stream has already been listened to.
I may listen a stream of a BLoC at just one place.
My code is the following.
main.dart
import 'package:flutter/material.dart';
import 'package:bottom_tab_bloc/app_state_bloc.dart';
import 'package:bloc_provider/bloc_provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return BlocProvider(
creator: (context, _bag) => AppStateBloc(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String activeTab = "tab1";
final bottomTabs = ["tab1", "tab2"];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(activeTab),
),
body: buildTab(activeTab),
bottomNavigationBar: BottomNavigationBar(
currentIndex: bottomTabs.indexOf(activeTab),
onTap: (int index) {
setState(() {
activeTab = bottomTabs[index];
});
},
items: bottomTabs.map((tab) =>
buildBnbItem(tab)
).toList(),
),
);
}
Widget buildTab(String tab) {
if (tab == "tab1") {
return TabOne();
} else if (tab == "tab2") {
return TabTwo();
}
}
BottomNavigationBarItem buildBnbItem (String tab) {
assert(bottomTabs.contains(tab));
if (tab == "tab1") {
return BottomNavigationBarItem(
title: Text('Tab1'),
icon: Icon(Icons.looks_one),
);
} else if (tab == "tab2") {
return BottomNavigationBarItem(
title: Text('Tab1'),
icon: Icon(Icons.looks_two),
);
}
}
}
class TabOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
final AppStateBloc bloc = BlocProvider.of<AppStateBloc>(context);
return StreamBuilder(
stream: bloc.outValue1,
builder: (context, snapshot) =>
Center(child: Text(snapshot.data.toString())),
);
}
}
class TabTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
final AppStateBloc bloc = BlocProvider.of<AppStateBloc>(context);
return StreamBuilder(
stream: bloc.outValue2,
builder: (context, snapshot) =>
Center(child: Text(snapshot.data.toString())),
);
}
}
app_state_bloc.dart
import 'dart:async';
import 'package:bloc_provider/bloc_provider.dart';
class AppStateBloc implements Bloc {
StreamController<int> _value1Controller
= StreamController<int>();
Sink<int> get _inValue1 => _value1Controller.sink;
Stream<int> get outValue1 => _value1Controller.stream;
StreamController<int> _updateValue1Controller
= StreamController<int>();
Sink<int> get updateValue1 =>
_updateValue1Controller.sink;
StreamController<int> _value2Controller
= StreamController<int>();
Sink<int> get _inValue2 => _value2Controller.sink;
Stream<int> get outValue2 => _value2Controller.stream;
StreamController<int> _updateValue2Controller
= StreamController<int>();
Sink<int> get updateValue2 =>
_updateValue2Controller.sink;
AppStateBloc(){
_inValue1.add(1);
_updateValue1Controller.stream.listen(_updateValue1);
_inValue2.add(2);
_updateValue2Controller.stream.listen(_updateValue2);
}
#override
void dispose() {
_value1Controller.close();
_updateValue1Controller.close();
_value2Controller.close();
_updateValue2Controller.close();
}
void _updateValue1(int value1) {
_inValue1.add(value1);
}
void _updateValue2(int value2) {
_inValue2.add(value2);
}
}
I can go to the TabTwo from TabOne only at the first time, but the error occurs when I go back to TabOne .
I also tried using StreamController<int>.broadcast() in app_state_bloc.dart, but snapshot.data is always null.
How can I implement BottomNavigationBar with BLoC pattern?
Why the streams are called more than twice, though I write each stream at just one place?
Is AppStateBloc.dispose() is called in this code? When and where AppStateBloc.dispose() is called?
Why broadcast stream's snapshot.data is always null?
Why the streams are called more than twice, though I write each stream
at just one place?
The error is related to the number of subscribers to the stream. StreamController by default only allows one subscriber. That's why TabOne works fine the first time, but breaks afterwards.
Is AppStateBloc.dispose() is called in this code? When and where
AppStateBloc.dispose() is called?
It would be called when the BlocProvider widget gets removed, but since it's being used as the app root, I guess this only happens when the app is closed.
Why broadcast stream's snapshot.data is always null?
Broadcast streams do not buffer events when there is no listener. Since you're writing to the stream before TabOne is created, the event is lost and you get null.
How can I implement BottomNavigationBar with BLoC pattern?
I guess it depends on your use case, but for this particular example, if you replace StreamController with rxdart's BehaviorSubject, it works fine, because then you'd have a broadcast stream that always sends you the last event.

Flutter close a Dialog inside a condition

I am trying to close a Dialog dynamically.
What I am actually trying to do is to change the content of the dialog depending on the information I have at the moment.
Starts with loading info and no button and after a few seconds could be an error with the OK button to close the Dialog Box.
class Dialogs{
loginLoading(BuildContext context, String type, String description){
var descriptionBody;
if(type == "error"){
descriptionBody = CircleAvatar(
radius: 100.0,
maxRadius: 100.0,
child: new Icon(Icons.warning),
backgroundColor: Colors.redAccent,
);
} else {
descriptionBody = new Center(
child: new CircularProgressIndicator(),
);
}
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context){
return AlertDialog(
title: descriptionBody,
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Center(child: Text(description))
],
),
),
);
}
);
}
}
So after creating the instance os the dialog and opening it
Dialogs _dialog = new Dialogs();
_dialog.loginLoading(context, "loading", "loading...");
// Close the dialog code here
don't know how to do it
// Call again the AlertDialog with different content.
https://docs.flutter.io/flutter/material/showDialog.html
The dialog route created by this method is pushed to the root navigator. If the application has multiple Navigator objects, it may be necessary to call Navigator.of(context, rootNavigator: true).pop(result) to close the dialog rather than just Navigator.pop(context, result).
So any one of the below should work for you
Navigator.of(context, rootNavigator: true).pop(result)
Navigator.pop(context, result)
You don't need to close and reopen the dialog. Instead let flutter handle the dialog update. The framework is optimised for just that.
Here is a working example app that you can use as a starting point (just add your own Dialogs class):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MyApp',
home: Login(
child: Home(),
),
);
}
}
class Home extends StatefulWidget {
final Dialogs dialog = Dialogs();
#override
State<StatefulWidget> createState() => HomeState();
}
class HomeState extends State<Home> {
#override
void didChangeDependencies() {
super.didChangeDependencies();
Future.delayed(Duration(milliseconds: 50)).then((_) {
widget.dialog.loginLoading(
context,
LoginStateProvider.of(context).type,
LoginStateProvider.of(context).description,
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Updating Dialog'),
),
body: Container(),
);
}
}
class Login extends StatefulWidget {
final Widget child;
Login({#required this.child});
#override
State<StatefulWidget> createState() => LoginState();
}
class LoginState extends State<Login> {
String type = 'wait';
String description = 'foo';
#override
void didChangeDependencies() {
super.didChangeDependencies();
Future.delayed(Duration(milliseconds: 2000)).then((_) {
setState(() {
type = 'error';
description = 'bar';
});
});
}
#override
Widget build(BuildContext context) {
return LoginStateProvider(widget.child, type, description);
}
}
class LoginStateProvider extends InheritedWidget {
final String type;
final String description;
LoginStateProvider(Widget child, this.type, this.description)
: super(child: child);
#override
bool updateShouldNotify(LoginStateProvider old) {
return type != old.type || description != old.description;
}
static LoginStateProvider of(BuildContext context) =>
context.inheritFromWidgetOfExactType(LoginStateProvider);
}

How to redraw whole app with new ThemeData?

I have 2 ThemeData vars and a SwitchListTile with the following code:
new SwitchListTile(
value: applyDarkTheme,
title: const Text('Appy dark theme?'),
onChanged: (bool value) {
setState(() {
applyDarkTheme = value;
});
})
the applyDarkTheme is a variable i check only when creating the app for the first time:
return new MaterialApp(
title: 'Test Application',
home: new MyHomePage(title: 'Test app'),
theme: settings.applyDarkTheme ? AppThemes.dark : AppThemes.light,
routes: _routes,
);
How can i redraw the app with the new ThemeData when i change the switch state?
You may want to consider nesting your MaterialApp within a StatefulWidget
Stateful and Stateless Widgets
Stateful Widget
The Flutter Gallery example app does this in their GalleryApp widget.
Use a simple, light StatefulWidget class e.g. GalleryApp
Which creates the corresponding GalleryAppState class.
This class has the GalleryTheme _galleryTheme instance variable
You need to set/change this value within the setState() method [ref].
You can then pass this them to the MaterialApp constructor
Here is a modified snippet
class GalleryApp extends StatefulWidget {
#override
GalleryAppState createState() => new GalleryAppState();
}
class GalleryAppState extends State<GalleryApp> {
GalleryTheme _galleryTheme = kAllGalleryThemes[0];
...
#override
Widget build(BuildContext context) {
Widget home = new GalleryHome(
galleryTheme: _galleryTheme,
onThemeChanged: (GalleryTheme value) {
setState(() {
_galleryTheme = value;
});
},
...
);
...
return new MaterialApp(
...
home: home,
);
}
}

Resources