Flutter changing Text value using RiverPod State management - flutter

In this below code i made simply code to change Text widget string, default string can be shown into Text widget but when i try to click FloatingActionButton is doesn't change.
clicking on HotReload cause chage it to new value, where i should change to resolve this issue?
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/all.dart';
class MyString extends StateNotifier<String> {
MyString() : super('Hello World');
void change(String text) => state = text;
}
final showHello = StateNotifierProvider<MyString>((ref) => MyString());
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Scaffold(
appBar: AppBar(
title: Text('SAMPLE'),
),
body: Center(
child: Consumer(
builder: (context, watch, _) {
final s = watch(showHello).state;
return Text(s);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read(showHello).change('Clicked On Button'),
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
),
);
}
}

April 2021 Update: As of Riverpod >= 0.14.0 the syntax has now been updated (scroll down for original answer). The following demonstrates how the example code provided in the question could be converted to the updated syntax:
import 'package:flutter/material.dart';
// Note importing hooks_riverpod/all.dart is now deprecated
import 'package:hooks_riverpod/hooks_riverpod.dart';
class MyString extends StateNotifier<String> {
MyString() : super('Hello World');
void change(String text) => state = text;
}
// Note the extra parameter, String, to specify what is provided by the Notifier
final showHello = StateNotifierProvider<MyString, String>((ref) => MyString());
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Scaffold(
appBar: AppBar(
title: Text('SAMPLE'),
),
body: Center(
child: Consumer(
builder: (context, watch, _) {
// Note here, state does not need to be specified.
final s = watch(showHello);
return Text(s);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Note that we now specify notifier here to access non-state
// attributes of the Notifier
context.read(showHello.notifier).change('Clicked On Button');
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
),
);
}
}
More on upgrading to Riverpod 0.14.0+ can be found here.
The following contains the original answer to the question. It is only valid for riverpod <= 0.13.
Change:
final s = watch(showHello).state;
to:
final s = watch(showHello.state);
The reason for this behavior is that you are watching the notifier itself, not its state. Since the notifier object isn't actually being changed, just the state property, a rebuild is not triggered (which is why you see the update upon hot reload). By watching the state itself, we rebuild whenever the state changes.
This concept extends to other kinds of providers, e.g. listening to the last exposed value of a StreamProvider.
A similar question I answered in the past.

Related

I can't navigate to another page

Please help me. I can't do basic navigation operations
I can't go to my shared page. I don't know why. When I want to route to another page with Navigator.push accrue to an error and I capture my error and add that at bottom of my question.
this is my main page
import 'package:flutter/material.dart';
import 'package:like/shared.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: homeScreen(context),
);
}
Scaffold homeScreen(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("shared"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Shared()),
);
},
child: Text("go")),
),
);
}
}
this is my shared page
import 'package:flutter/material.dart';
import 'package:like/main.dart';
void main() => runApp(Shared());
class Shared extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: Scaffold(
appBar: AppBar(
title: Text('Material App Bar'),
),
body: Center(
child: Container(
child: Text('Hello World'),
),
),
),
);
}
}
Both of them do not work.
For more information about the error, you can see the photo of the error
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Shared()),
);
Shared() is not a widget
remove this line from the top of your page:
import 'package:like/shared.dart';
Shared() is a package

Problem with Null Check Operator Being Called on a Null Value in a Use Case Involving Named Routing with Arguments

[PLEASE SEE EDIT BELOW]
I am using named routing (with arguments) to send a user from page2 back to page1 in my app when a button is pressed:
onPressed: () {
bool resumeProcess = true;
Navigator.pushNamed(context, '/',
arguments: RouteArguments(resumeProcess: resumeProcess));
},
My named routing is set up in my MaterialApp:
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
final RouteArguments args =
ModalRoute.of(context)!.settings.arguments as RouteArguments;
return MaterialApp(
title: 'Reactive BLE Test',
theme: ThemeData(
primarySwatch: Colors.pink,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: '/',
routes: {
'/': (context) => MyHomePage(
title: 'Reactive BLE Test',
resumeProcess: args.resumeProcess,
),
'/myWifiPage': (context) => const MyWifiPage(),
},
navigatorKey: navigatorKey,
);
}
}
I'm using the following class in conjunction with ModalRoute to retrieve args.resumeProcess:
class RouteArguments {
final bool resumeProcess;
RouteArguments({required this.resumeProcess});
}
and
final RouteArguments args =
ModalRoute.of(context)!.settings.arguments as RouteArguments;
I'm unable to retrieve args.resumeProcess because my bang operator is being used on a null value at runtime.
I have tried different ways to solve this problem with no success. There are some good posts about this, but none seem to fit my use case (or maybe I'm missing something).
Is there a better way to send these arguments, or is there a way to deal with this null problem at runtime?
[BEGIN EDIT HERE]
While trying to fix the problem above, I turned to the recipe for doing this provided in the Flutter Docs: Pass arguments to a named route. This resulted in the following changes.
New ScreenArguments Class:
class ScreenArguments {
final String title;
final String resumeProcess;
ScreenArguments({required this.title, required this.resumeProcess});
}
New ExtractArgumentsScreen Class
class ExtractArgumentsScreen extends StatelessWidget {
const ExtractArgumentsScreen({super.key});
static const routeName = '/';
#override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as ScreenArguments;
return Scaffold(
appBar: AppBar(
title: Text(args.title),
),
body: Center(
child: Text(args.resumeProcess),
),
);
}
}
My MaterialApp is located in MyApp. This is where I register the widget in the routes table per the docs. I've commented out the old routes.
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Reactive BLE Test',
theme: ThemeData(
primarySwatch: Colors.pink,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: '/',
// routes: {
// '/': (context) => MyHomePage(
// title: 'Reactive BLE Test',
// resumeProcess: args.resumeProcess,
// ),
// '/myWifiPage': (context) => const MyWifiPage(),
// },
routes: {
ExtractArgumentsScreen.routeName: (context) =>
const ExtractArgumentsScreen(),
},
navigatorKey: navigatorKey,
);
}
}
And finally, I navigate to the widget in my second page (returning from page 2 to page 1) using Navigator.pushNamed().
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: const Text('Reactive BLE Test'),
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () {
String title = 'Reactive BLE Test';
String resumeProcess = 'true';
Navigator.pushNamed(
context,
ExtractArgumentsScreen.routeName,
arguments: ScreenArguments(
title: title,
resumeProcess: resumeProcess,
),
);
},
),
actions: const [],
),
body: Column(
children: <Widget>[
I get a new, but similar error:
type 'Null' is not a subtype of type 'ScreenArguments' in type cast
Can anyone tell me what am I doing wrong? Thanks in advance.
I think you are calling it on the wrong place. these ModalRoute.of(context) must be associated with context that having modal routes, or routes. in this case MaterialApp which hold the routes. But you are calling it before the MaterialApp is created. try to move it into inside of MyHomePage and read the arguments inside there instead of passing it as arguments on the material app Routes.
for example:
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Reactive BLE Test',
theme: ThemeData(
primarySwatch: Colors.pink,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: '/',
routes: {
'/': (context) => MyHomePage(
title: 'Reactive BLE Test',
),
'/myWifiPage': (context) => const MyWifiPage(),
},
navigatorKey: navigatorKey,
);
}
and inside your MyHomePage widget:
#override
Widget build(BuildContext context) {
final RouteArguments args =
ModalRoute.of(context)!.settings.arguments as RouteArguments;
var resumeProcess = args.resumeProcess;
return Scaffold(
// your home page class
);
}

Show snack bar to handling missing URL receivers

I used the url_luncher package and it suggests handle the missing URL receiver in case of target platform can not handle the URL.
So I create a function to handle of onTap of CardTile widget and dial the phone number and if the target platform can not handle the request it shows a snake bar to inform the user in UI.
But I have two problems 1) if using an anonymous function I get a runtime error and my code would be wordly and long
Unhandled Exception: No ScaffoldMessenger widget found.
MyApp widgets require a ScaffoldMessenger widget ancestor.
The specific widget that could not find a ScaffoldMessenger ancestor was:
MyApp
The ancestors of this widget were:
[root]
Typically, the ScaffoldMessenger widget is introduced by the MaterialApp at the top of your application widget tree.
if use function name onTap of CardTile widget for example onTap : _urlLauncherFunction(context) I can not pass BuildContext context argument to the function and get a compile error
This expression has a type of 'void' so its value can't be used.
I could not figure out what did wrong so please guide and help me to solve this.
I paste the anonymous function version here
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:url_launcher/url_launcher.dart';
void main() {
runApp(MyApp());
}
final telLaunchErrorSnackBar = SnackBar(
content:
Text('Your device can not handle this url'));
final String _myPhoneNumber = '+9812345678';
//use xml scheme to trigger error otherwise it should be tel
final Uri _telUri = Uri(scheme: 'xml', path: _myPhoneNumber);
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Column(
children: [
CircleAvatar(
backgroundImage: AssetImage('images/image.jpg'),
),
Card(
child: ListTile(
leading: Icon(
Icons.phone,
),
title: Text(
'00 123 45657',
),
onTap: () async {
String telUri = _telUri.toString();
await canLaunch(telUri)
? await launch(telUri)
: ScaffoldMessenger.of(context)
.showSnackBar(telLaunchErrorSnackBar);
},
),
),
],
),
),
),
);
}
}
When I code as this , flutter throw a error.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// home: MyHomePage(title: 'Flutter Demo Home Page'),
home: Scaffold(
body: SafeArea(
child: Container(
alignment: Alignment.center,
child: GestureDetector(
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
onTap: (){
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('content')));
},
),
),
),
),
);
}
}
then I change
...
MaterialApp(
home:Scafford(...),
)
...
to
...
MaterialApp(
home:Home(),
)
...
class Home extends StatelessWidget {
#override
Widget build(BuildContext context){
return Scaffold(...);
}
}
It works.

Save a theme (Flutter/Dart)

Good morning,
Here I have two buttons that change the theme of my application (light and dark). When I reload my app the theme is not the one I selected last. I would like the application to back up the last theme used locally. You may need to save just a number that indicates which theme used the last one. . . But I don’t know at all how to do this?
Here’s the code: main.dart
import 'package:flutter/material.dart';
import 'package:animated_splash_screen/animated_splash_screen.dart';
import 'package:watch/nav.dart';
import 'package:page_transition/page_transition.dart';
import 'package:watch/blocs/theme.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<ThemeChanger>(
builder: (_) => ThemeChanger(ThemeData.dark()),
child: MaterialAppWithTheme(),
);
}
}
class MaterialAppWithTheme extends StatelessWidget {
#override
Widget build(BuildContext context) {
final theme = Provider.of<ThemeChanger>(context);
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: theme.getTheme(),
home: AnimatedSplashScreen(
duration: 3000,
splash: "",
splashTransition: SplashTransition.slideTransition,
pageTransitionType: PageTransitionType.downToUp,
nextScreen: Nav(),
),
);
}
}
settings.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:watch/blocs/theme.dart';
import 'package:watch/constants.dart';
class Parametres extends StatelessWidget {
#override
Widget build(BuildContext context) {
ThemeChanger _themeChanger = Provider.of<ThemeChanger>(context);
return Scaffold(
appBar: AppBar(
title: Text('Paramètres', style: kAppBarStyle,),
elevation: 0,
automaticallyImplyLeading: false,
leading: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Icon(
Icons.arrow_back,
),
),
),
body: Container(
child: Column(
children: <Widget>[
FlatButton(
onPressed: () => _themeChanger.setTheme(
ThemeData(
bottomNavigationBarTheme: bNavBar,
scaffoldBackgroundColor: kBlackMedium,
brightness: Brightness.dark,
iconTheme: bIcons,
)),
child: Text('Dark Theme')),
FlatButton(
onPressed: () => _themeChanger.setTheme(
ThemeData(
bottomNavigationBarTheme: lNavBar,
scaffoldBackgroundColor: Colors.white,
brightness: Brightness.light,
iconTheme: lIcons,
primaryColor: kWhite,
)),
child: Text('Light Theme')),
],
),
),
);
}
}
Thank you
Shared preference is best option for it. Since I don't know about your ThemeChanger class I add here my theme class first:
class MyThemeModel extends ChangeNotifier{
ThemeData _themedata;
MyThemeModel(bool isActive){
if(isActive == null){
getThemeData;
}
else{
if(isActive){
_themedata = sleepTheme;
}
else{
_themedata = morningTheme;
}
}
}
ThemeData get getThemeData => _themedata;
void setThemeData(ThemeData data){
_themedata = data;
notifyListeners();
}
}
In main.dart
void main() async{
var isSleepActive;
if(SharedPrefHelper.prefInstance.checkContains(SharedPrefKeys.ISMORNING)){
isSleepActive = SharedPrefHelper.prefInstance.getBool(SharedPrefKeys.ISMORNING);
}
else{
isSleepActive = false;
}
runApp(MultiProvider(
providers: [
ChangeNotifierProvider(
builder: (context) => MyThemeModel(isSleepActive),
)
],
child: MyApp(),
)
);
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: Provider.of<MyThemeModel>(context).getThemeData,
title: 'Theme App',
home: AnimatedSplashScreen(
duration: 3000,
splash: "",
splashTransition: SplashTransition.slideTransition,
pageTransitionType: PageTransitionType.downToUp,
nextScreen: Nav(),
),
debugShowCheckedModeBanner: false,
);
}
In order to change theme with flat button:
FlatButton(
onPressed: () => myThemeModel.setThemeData(
ThemeData(
bottomNavigationBarTheme: lNavBar,
scaffoldBackgroundColor: Colors.white,
brightness: Brightness.light,
iconTheme: lIcons,
primaryColor: kWhite,
)),
child: Text('Light Theme')),
Use the Shared Preference package and there you can store simple values as key pair values.Load that data in the init of the initial screen so that you can display the screen according to the theme
You should use local memory to save theme.
You can use shared preference or hive db or sqflite or other database system.
About changing theme you can use Cubit,Bloc,Provider or etc or even if ValueNotifier.
However you should wrap your MaterialApp or CupertinoApp with "your state management widget"
And add some Logic
OR you can use some library
Library to change theme

Flutter stateless UI life cycle

I have a Flutter app which is based on StatelessWidget. I saw an example with StatefulWidget which allows the programmer to perform some actions in the dispose() method. I am trying to see if the StatelessWidget also provides such feature?
import 'package:flutter/material.dart';
import 'routes.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: colorCustom,
fontFamily: 'Roboto',
),
initialRoute: '/',
routes: routes,
);
}
}
UPDATED
import 'package:flutter/material.dart';
import 'routes.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Future<bool> _exitApp(BuildContext context) {
return showDialog(
context: context,
child: new AlertDialog(
title: new Text('Do you want to exit this application?'),
content: new Text('We hate to see you leave...'),
actions: <Widget>[
new FlatButton(
onPressed: () => Navigator.of(context).pop(false),
child: new Text('No'),
),
new FlatButton(
onPressed: () => Navigator.of(context).pop(true),
child: new Text('Yes'),
),
],
),
) ??
false;
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: WillPopScope(
child: MaterialApp(
theme: ThemeData(
primarySwatch: colorCustom,
fontFamily: 'Roboto',
),
initialRoute: '/',
routes: routes
),
onWillPop: () => _exitApp(context)
)
);
}
Hm, after updating the code, the AlertDialog does not pop up though?
The short answer is no, there is no method similar to dispose() available in a StatelessWidget since as the name suggests, it does not maintain a State.
The long answer is the dispose() mehtod -
The framework calls this method when this State object will never build again. After the framework calls dispose, the State object is considered unmounted and the mounted property is false. It is an error to call setState at this point.
This stage of the lifecycle is terminal: there is no way to remount a State object that has been disposed.
Since the StatelessWidget has no State associated with it, there is no dispose() method.
A similar solution for StatelessWidgets -
So what should we do if we want to run some code only after theStatelessWidgets are popped from the navigation stack or return some data from the StatelessWidget?
Well, this Flutter docmentation page has a this explained along with an example. Hope this helps!