flutter - Variable that changed the value using sharedPreferences does not work properly -Has a simple example code - flutter

I created a button that allows users to change the size of letters.
I hope that the font size value will remain changed even if I run the app again.
But in my code, it goes back to its original state.I made a simple code of the problem I faced. I made three files.
1.HomePage.dart
import 'dart:async';
import 'dart:convert';
// import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import './main.dart';
import './count_page.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late CountPage _countPage;
#override
Widget build(BuildContext context) {
String prefsFont = "prefs_font";
_countPage = Provider.of<CountPage>(context, listen: true);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.green,
title: Text('Test_Code'),
centerTitle: true, // 중앙 정렬
elevation: 0.0,
),
body: Container(
color: Colors.white,
child: Text('Font_size',style: TextStyle(fontSize: _countPage.font),),
),
bottomNavigationBar: BottomAppBar(
color: Colors.lime,
child: Container(
height: 200,
child: Row(
children: [
IconButton(
onPressed: () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setDouble('prefs_font', 10);
setState(() {
_countPage.font = (prefs.getDouble('prefs_font') ?? 40) ;
});
},
icon: Icon(Icons.looks_one_outlined)),
IconButton(
onPressed: () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setDouble('prefs_font', 40);
setState(() {
_countPage.font = (prefs.getDouble('prefs_font') ?? 40) ;
});
},
icon: Icon(Icons.looks_two_outlined)),
IconButton(
onPressed: () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setDouble('prefs_font', 80);
setState(() {
_countPage.font = (prefs.getDouble('prefs_font') ?? 40) ;
});
},
icon: Icon(Icons.looks_3_outlined)),
],
),
),
),
);
}
}
2.main.dart
import 'dart:convert';
// import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import './HomePage.dart';
import 'count_page.dart';
void main() {
// WidgetsFlutterBinding.ensureInitialized();
// MobileAds.instance.initialize();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => CountPage(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
),
home: HomePage(),
),
);
}
}
3.count_page.dart
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class CountPage extends ChangeNotifier {
double _font = 40;
double get font => _font;
set font(double font) {
_font = font;
}
}
I made it small so that the composition is similar to the project I am working on.
The value changes even if you press the button, but it goes back to the beginning when you run the app again.
What is the problem?

So basically what's happening here is, you're only storing the font inside your SharedPrefrence. But you're not fetching it back when the app starts. Your CountPage class isn't storing the font size. It's shared preference that's storing it. So you have to just fetch the data from SharedPrefrence on the app start. And then use it in your code. A small example will be
Initialize SharedPrefrence so you can use later.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final sharedPref = await SharedPrefrence.getInstance(); // Instance will be created.
runApp(const MyApp());
}
2.Then make your class use SharedPrefrence instance by default
class CountPage extends ChangeNotifier {
final SharedPrefrence pref;
CountPage({required this.pref});
double get font => pref.getDouble('prefs_font') ?? 40;
setFont(double font) async {
await prefs.setDouble('prefs_font', font);
notifyListeners(); //This is necessary so that when the value changes the UI gets an update.
}
}
Then pass the SharedPrefrence instance to that class, so it can use it.
class MyApp extends StatelessWidget {
final SharedPrefrence pref;
const MyApp({Key? key, required this.pref}) : super(key: key); // Getting the instance from main.
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => CountPage({pref: pref}),//Passing the instance.
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
),
home: HomePage(),
),
);
}
}
Also on your save button it looks like you're using a getter to set the value, which shouldn't work. But now you can just call it like this
onPressed: () async {
setState(() { // Now this setState is optional bcz you're saying the notifier to notify it's listeners, whenever you're setting the value.
await _countPage.setFont(\* font size for this button */);
});
},

Related

How to write test for flutter package Flutter WeChat Assets Picker

I am trying to write a test on this flutter package https://pub.dev/packages/wechat_assets_picker using the Mocktail package https://pub.dev/packages/mocktail, but this package does not seem to have test in the documentation.
I have included the minimum reproducible example. The test file is currently not working, it is included as an example test code using the flutter Mocktail package.
It is supposed to mock AssetPicker.pickAssets to test whether it is actually called with the correct arguments. I am running in IOS simulator, we will need to add this key in ios/Runer/Info.plist, otherwise the simulator will close unexpectedly after clicking the add button.
main.dart
import 'package:flutter/material.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.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: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
final List<AssetEntity>? result =
await AssetPicker.pickAssets(context);
},
tooltip: 'Add photo',
child: const Icon(Icons.add),
),
);
}
}
widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:wechat/main.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
class _MockAssetPicker extends Mock implements AssetPicker {}
void main() {
testWidgets('It should call the WeChat asset picker',
(WidgetTester tester) async {
await tester.pumpWidget(const MyApp());
final BuildContext context = tester.element(find.byType(MyApp));
final assetPickerMock = _MockAssetPicker();
when(() => assetPickerMock.pickAssets(context)).thenAnswer((_) => Future.value([
const AssetEntity(
id: 'id1',
typeInt: 1,
width: 100,
height: 100,
),
]));
AssetPicker.instance = assetPickerMock;
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
verify(() => assetPickerMock.pickAssets(context)).called(1);
});
}
Mocking the picker is supported by the separated picker delegate: https://github.com/fluttercandies/flutter_wechat_assets_picker/pull/315
TL;DR, build your own delegate first, then set it through AssetPicker.setPickerDelegate(TestAssetPickerDelegate());

Shared Preferences key value not changing

I want to write code that directs the user to a welcome page if it's the first time the app is being run. After the user logs in, any subsequent launches of the app direct the user to log in, skipping the welcome page. It seems that when I try to set the value of my key upon logging in, it's not changing, because after logging in, closing the app and launching it again it's still going to the welcome page. How do I fix this? I'm not sure whether the issue is with the initialisation or the setting of the value upon login.
Here's the initialisation of my app:
import 'package:screens/welcomepages/signup_or_login.dart';
import 'package:screens/welcomepages/welcome.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:config/theme.dart';
import 'package:shared_preferences/shared_preferences.dart';
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarBrightness: Brightness.dark));
runApp(MyApp(prefs: await _prefs));
}
class MyApp extends StatefulWidget {
const MyApp({Key? key, this.title, required this.prefs})
: super(key: key);
final String? title;
final SharedPreferences prefs;
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late SharedPreferences prefs;
#override
void initState() {
super.initState();
initializePreference();
}
Future<void> initializePreference() async {
prefs = await SharedPreferences.getInstance();
setState(() {
prefs.setBool("hasLoggedIn", false);
});
}
#override
Widget build(BuildContext context) {
if (prefs.getBool("hasLoggedIn") == false) {
return MaterialApp(
theme: theme(),
debugShowCheckedModeBanner: false,
home: Welcome(prefs: prefs),
);
}
return MaterialApp(
theme: theme(),
debugShowCheckedModeBanner: false,
home: SignUpOrLogIn(prefs: prefs),
);
}
}
And here's the relevant parts of my log in page
import 'package:services/auth/auth_service.dart';
import 'package:widgets/text_fields_widgets/email_textfield.dart';
import 'package:widgets/text_fields_widgets/password_textfeild.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Login extends StatefulWidget {
const Login({Key? key, required this.prefs}) : super(key: key);
final SharedPreferences prefs;
#override
_LoginState createState() => _LoginState(prefs);
}
class _LoginState extends State<Login> {
late final SharedPreferences prefs;
_LoginState(SharedPreferences prefs);
Future<void> initializePreference() async {
prefs = await SharedPreferences.getInstance();
}
#override
void initState() {
super.initState();
initializePreference().whenComplete(() {
setState(() {
prefs.getBool("isFirstRun");
});
});
}
#override
Widget build(BuildContext context) {
Material btnLogin = loginBTN(context);
return Scaffold(
.....
Padding(
padding: const EdgeInsets.all(8.0),
child: btnLogin,
)
}
Material loginBTN(BuildContext context) {
// ignore: unused_local_variable
final btnLogin = Material(
elevation: 5,
borderRadius: BorderRadius.circular(30),
color: Theme.of(context).primaryColor,
child: MaterialButton(
padding: const EdgeInsets.fromLTRB(20, 15, 20, 15),
minWidth: MediaQuery.of(context).size.width,
onPressed: () async {
setState(() {
prefs.setBool("hasLoggedIn", true);
});
setState(
() => loadingAnimation = true,
);
await Future.delayed(const Duration(seconds: 1));
setState(
() => loadingAnimation = false,
);
await Authservice().logInMethod(
emailController.text, passwordController.text, context, _formKey);
},
child: loadingAnimation
? const CircularProgressIndicator(
color: Colors.white,
)
: const Text(
"Log in",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 24, color: Colors.white),
),
),
);
return btnLogin;
}
}
Sorry for how lengthy this is, tried to shorten it to only provide what's relevant.
Your are calling initializePreference() inside initState() and that function set hasLoggedIn to false. So even if you set it to true in your login page, when restarting the app it will be set again to false.
Try this brother
if (prefs.getBool("hasLoggedIn") == null || prefs.getBool("hasLoggedIn") == false) {
return MaterialApp(
theme: theme(),
debugShowCheckedModeBanner: false,
home: Welcome(prefs: prefs),
);
}

Flutter SharedPreferences non-nullable instance fields

I have a problem when I want to using the shared preferences to store and display the value. On the code, I would need to create the public class for the bellow function to use.
However it would return the follow error:
error: Non-nullable instance field 'sharedPreferences' must be initialized. (not_initialized_non_nullable_instance_field at [login] lib/main.dart:32)
SharedPreferences sharedPreferences; //line:32
Here is my full code.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:login/login_page.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Code Land",
debugShowCheckedModeBanner: false,
home: MyHomePage(),
theme: ThemeData(
accentColor: Colors.white70
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
SharedPreferences sharedPreferences;
#override
void initState() {
super.initState();
checkLoginStatus();
}
checkLoginStatus() async {
sharedPreferences = await SharedPreferences.getInstance();
if(sharedPreferences.getString("token") == null) {
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (BuildContext context) => LoginPage()), (Route<dynamic> route) => false);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Code Land", style: TextStyle(color: Colors.white)),
actions: <Widget>[
FlatButton(
onPressed: () {
sharedPreferences.clear();
sharedPreferences.commit();
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (BuildContext context) => LoginPage()), (Route<dynamic> route) => false);
},
child: Text("Log Out", style: TextStyle(color: Colors.white)),
),
],
),
body: Center(child: Text("Main Page")),
drawer: Drawer(),
);
}
}```
change the line like this for the compiler to know that you will initialize it later:
late SharedPreferences sharedPreferences;
This should do the trick, if you don't intend to initialize sharedPref right away.
SharedPreferences? sharedPreferences;
Dart sound null safety has been enabled as default in flutter since 2.2 if I'm correct.
Check out this article for a starter on the topic.
https://dart.dev/null-safety
From the Dart documentation: "The late modifier lets you use non-nullable types and final in places you otherwise might not be able to, at the expense of runtime checking. It also gives you lazy-initialized fields." https://dart.dev/null-safety/understanding-null-safety
Your SharedPreferences declaration should have the late modifier:
late SharedPreferences sharedPreferences;

How to get a picked file to show in a another widget

I wanted to build a feature where a user will be typing texts then may wanted to add a file, pick an image then the picked image will now be returned at the original text screen with the texts there too. I have develop the feature for inputting the texts my text feature then i am stuck at how the picked image will be returned to that same text place. Please how can i get this feature?
First You Need to Add dependencies image_picker: ^0.6.4 Used the latest version, Second
pickup an image
class _MyHomePageState extends State<MyHomePage> {
File _iamge;
Future CameraImage() async{
var iamge =await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
_iamge = iamge;
});
}
Here Is Full Code To know Better Understand.
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
File _iamge;
Future CameraImage() async{
var iamge =await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
_iamge = iamge;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Container(
height: 500,
width: double.infinity,
color: Colors.blue,
child: _iamge==null?Center(child: Text("NO Image
Select")):Image.file(_iamge),
),
Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FloatingActionButton(onPressed: (){
CameraImage();
},child: Icon(Icons.camera),),
SizedBox(width: 20,),
FloatingActionButton(onPressed: (){},
child: Icon(Icons.photo_library),),
],
)
],
),
);
}
}
receive.dart
import 'package:flutter/material.dart';
import 'package:flutterapp/data.dart';
import 'package:provider/provider.dart';
class ReceiveData extends StatelessWidget {
#override
Widget build(BuildContext context) {
final providerdata= Provider.of<Data>(context);
return Scaffold(
body: Center(
child:
Text(providerdata.value.toString(),style:TextStyle(fontSize:50),),
),
);
}
}

Flutter application hangs when calling await rootBundle.loadString(...) from main()

I found this answer about storing global configuration into globals.dart.
How can I load configuration into it from assets/config.json?
I've tried like this:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:convert';
import 'globals.dart' as globals;
void main() async {
globals.config = jsonDecode(await rootBundle.loadString('assets/config.json'));
runApp(MyApp());
}
class MyApp extends StatelessWidget {
...
}
The application starts with a white screen. Nothings happens, no errors. I guess that await rootBundle.loadString(...) causes the application to hang.
You can copy paste run full code below
You need to add WidgetsFlutterBinding.ensureInitialized() in main()
without this line will produce white screen
globals.dart
Map<String, dynamic> config = {};
config.json
{
"id": "1",
"name": "abc"
}
code snippet
void main() async{
WidgetsFlutterBinding.ensureInitialized();
globals.config = jsonDecode(await rootBundle.loadString('assets/config.json'));
runApp(MyApp());
}
pubspec.yaml
assets:
- assets/
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:convert';
import 'globals.dart' as globals;
void main() async{
WidgetsFlutterBinding.ensureInitialized();
globals.config = jsonDecode(await rootBundle.loadString('assets/config.json'));
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return 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> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(globals.config["id"]),
Text(globals.config["name"]),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}