LateInitializationError SharedPreferences - flutter

I am trying to make a singleton of sharedpreferences, but I get a failure of not initialized
(LateError (LateInitializationError: Field 'prefs' has not been initialized.)).
I don't know what could be wrong. I suppose that it could be for the late of SharedPreferences.dart, or it lacks somewhere to define the initialization?
Personaje_home.dart
class PersonajeHomePage extends StatefulWidget {
const PersonajeHomePage({Key? key}) : super(key: key);
#override
State<PersonajeHomePage> createState() => _PersonajeHomePageState();
}
class _PersonajeHomePageState extends State<PersonajeHomePage> {
#override
Widget build(BuildContext context) {
var estiloTexto = EstiloTextos();
final proPersonaje = Provider.of<PersonajeProvider>(context);
return Scaffold(
backgroundColor: Colors.grey.shade300,
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
Text(proPersonaje.leerLvLPersonaje.toString()),
],
),
),
),
);
}
}
SharedPreferences.dart
class SharedPrefsPersonaje {
static final SharedPrefsPersonaje _instancia = SharedPrefsPersonaje._internal();
factory SharedPrefsPersonaje() {
return _instancia;
}
SharedPrefsPersonaje._internal();
late SharedPreferences prefs;
initPrefs() async {
prefs = await SharedPreferences.getInstance();
}
int get readExpPersonaje {
return prefs.getInt("experiencia_personaje") ?? 0;
}
set saveExpPersonaje(int value) {
prefs.setInt("experiencia_personaje", value);
}
int get readLvLPersonaje {
return prefs.getInt("nivel_personaje") ?? 0;
}
set saveLvLPersonaje(int value) {
prefs.setInt("nivel_personaje", value);
}
void eliminar() {
prefs.clear();
}
}
main.dart
void main() {
inicializamosCargaDatos();
runApp(AppState());
}
class AppState extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
//Cargamos datos del JSON, servidor
ChangeNotifierProvider(
create: (_) => PersonajeProvider(),
lazy: false,
),
],
child: MyApp(),
);
}
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: "My APP",
initialRoute: "/",
getPages: [
GetPage(
name: "/",
page: () => const PersonajeHomePage(),
),
],
);
}
}
Future<void> inicializamosCargaDatos() async {
WidgetsFlutterBinding.ensureInitialized();
final prefs = SharedPrefsPersonaje();
await prefs.initPrefs();
}
Provider_personajes.dart
class PersonajeProvider with ChangeNotifier {
final prefs = SharedPrefsPersonaje();
int xpPersonaje = 0;
int lvlPersonaje = 0;
get leerXPPersonaje {
if (prefs.readExpPersonaje != 0) {
return xpPersonaje = prefs.readExpPersonaje;
}
return xpPersonaje;
}
set guardarXPPersonaje(int xp) {
xpPersonaje = xp + xpPersonaje;
prefs.saveExpPersonaje = xpPersonaje;
notifyListeners();
}
get leerLvLPersonaje {
if (prefs.readLvLPersonaje != 0) {
return lvlPersonaje = prefs.readLvLPersonaje;
}
return lvlPersonaje;
}
set guardarLvLPersonaje(int lvl) {
lvlPersonaje = lvl + lvlPersonaje;
prefs.saveLvLPersonaje = lvlPersonaje;
notifyListeners();
}
void borrarDatos() {
prefs.eliminar();
}
}

You need to do something similar to what you do when using firebase:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await inicializamosCargaDeDatos();
runApp(const MyApp());
}
This way, you're making sure that nothing in your app is executed before your instance is initialized.

Related

Replicate my KeyboardListener in all my screens in Flutter

I have an app that works with a scanner. I'm reading the scan just through text. This seems to happen in almost every screen of my app. I tried to control this through my MyApp widget, but I have no idea how to call the current screen (widget) to process the barcode according this current screen. I'm encapsulating everything through a KeyboardListener. is there a better approach for this problem? I was thinking also to create a abstract class holding all the scanner functionality inside.
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:scanner/models/beep.dart';
import 'package:scanner/screens/dashboard.dart';
import 'package:scanner/screens/location_info.dart';
import 'package:scanner/screens/login.dart';
import 'package:scanner/screens/relocation.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
static final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<LogicalKeyboardKey> keys = [];
dynamic screen;
late Future<Beep> futureUser;
Future<Beep> fetchBeep(String code) async {
try {
final response = await http.get(Uri.parse('http://192.168.224.8:8000/restapi/beeps/${code}/'));
if (response.statusCode == 200) {
return Beep.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load beep');
}
} catch (e) {
debugPrint(e.toString());
throw Exception('Failed!!!!!');
}
}
String _getBarcode(List<LogicalKeyboardKey> keys) {
String code = '';
for (var key in keys) {
if (key.keyLabel != 'Shift Left') {
code += key.keyLabel;
}
}
return code;
}
#override
Widget build(BuildContext context) {
return KeyboardListener(
autofocus: true,
focusNode: FocusNode(),
onKeyEvent: (event) {
if (event is KeyUpEvent) {
final key = event.logicalKey;
if (key != LogicalKeyboardKey.enter) {
setState(() {
keys.add(key);
});
}
else {
String barcode = _getBarcode(keys);
Future<Beep> b = fetchBeep(barcode);
b.then((value) {
keys = [];
debugPrint('HERE I MUST CALL THE SCREEN !!!');
// screen.process(value);
});
}
}
},
child: MaterialApp(
title: 'Protonic',
theme: ThemeData(
primaryColor: Color.fromARGB(255, 17, 6, 65),
),
initialRoute: '/',
onGenerateRoute: (settings) {
String? name = settings.name;
if (name == '/') {
return MaterialPageRoute(builder: (_) => const LoginScreen());
}
if (name == '/dashboard') {
return MaterialPageRoute(builder: (_) => const DashboardScreen());
}
if (name == '/relocation') {
return MaterialPageRoute(builder: (_) => const RelocationScreen());
}
if (name == '/location_info') {
return MaterialPageRoute(builder: (_) => const LocationInfoScreen());
}
return null;
},
)
);
}
}

Flutter variable is look like it has two datas

I want to use a variable, which i want to fill with firebase data.
In the Firebase section it fills it up with the correct data and print it correctly to log, but after that in the Scaffold its looks like it using the data in the starting declaration.
But why? It's one variable with two data? So at the end of the code in the Text('$masik') i want to use the firebase data, not the starting string.
What am i doing wrong?
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String masik = '';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Firebase',
home: AddData(),
);
}
}
class AddData extends StatelessWidget {
#override
Widget build(BuildContext context) {
String doksi = 'valami más';
var ezlett;
String masik = 'minden';
String sajt = '';
final Stream<QuerySnapshot> collectionStream = FirebaseFirestore.instance.collection('zenek').snapshots();
FirebaseFirestore.instance
.collection('zenek')
.doc(doksi)
.get()
.then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
String ezegyszoveg = documentSnapshot.data().toString();
print('Document exists on the database $ezegyszoveg');
ezlett = ezegyszoveg.substring(9, ezegyszoveg.indexOf(','));
print(ezlett);
masik = ezegyszoveg.substring(ezegyszoveg.indexOf('text: ')+6, ezegyszoveg.indexOf('}'));
print(masik);
}
});
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.green,
title: const Text("próba"),
),
body:Row(
children: [
Text('$masik'),
],
)
);
}
}
Something like this example will work to update the UI when there is new data has been updated
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String masik = '';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Firebase',
home: AddData(),
);
}
}
class AddData extends StatefulWidget {
#override
State<AddData> createState() => _AddDataState();
}
class _AddDataState extends State<AddData> {
String doksi = 'valami más';
var ezlett;
String masik = 'minden';
String sajt = '';
#override
void initState() {
super.initState();
updateTheUi();
}
void updateTheUi() {
final Stream<QuerySnapshot> collectionStream =
FirebaseFirestore.instance.collection('zenek').snapshots();
FirebaseFirestore.instance
.collection('zenek')
.doc(doksi)
.get()
.then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
String ezegyszoveg = documentSnapshot.data().toString();
ezlett = ezegyszoveg.substring(9, ezegyszoveg.indexOf(','));
masik = ezegyszoveg.substring(
ezegyszoveg.indexOf('text: ') + 6, ezegyszoveg.indexOf('}'));
setState(() {});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.green,
title: const Text("próba"),
),
body: Row(
children: [
Text('$masik'),
],
));
}
}
hey u r doing a silly mistake u have to use setState when u update your variable :
setState(){
masik = ezegyszoveg.substring(ezegyszoveg.indexOf('text: ')+6,ezegyszoveg.indexOf('}'));
} // this line put in setState in your code

getting data after building widgets

I am trying to get data from Database, but my widget is built before I can get them...
class CategoriesWidget extends StatefulWidget {
#override
_CategoriesWidgetState createState() => _CategoriesWidgetState();
}
class _CategoriesWidgetState extends State<CategoriesWidget> {
SharedPreferences prefs;
String token;
var _isInit = false;
#override
void initState() {
if (!_isInit) {
super.initState();
fetchCat();
_isInit = true;
}
}
var categories = {};
fetchCat() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
token = prefs.getString('api_token');
});
await fetchCategories(token).then((result) {
categories = result[1];
print(categories);
print(result[1]);
});
}
#override
Widget build(BuildContext context) {
final deviceSize = MediaQuery.of(context).size;
print('2');
return Column(
// code here
);
}
}
you can see that I print 1 and 2 to see which one is getting the first and I got as result 2 then 1.
You should use a FutureBuilder.
#override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: _fetchCat(),
builder: (context, snapshot) => snapshot.hasData
? MyWidget(data: snapshot.data)
: Text('Loading...'),
);
}
And with a FutureBuilder, your Widget could probably stay Stateless.
Here is a Minimal Working Example:
Full source code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'StackOverflow Answer',
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: CategoriesWidget()),
);
}
}
class CategoriesWidget extends StatefulWidget {
#override
_CategoriesWidgetState createState() => _CategoriesWidgetState();
}
class _CategoriesWidgetState extends State<CategoriesWidget> {
Future<String> _fetchCat() async {
await Future.delayed(Duration(seconds: 2));
return 'Category';
}
#override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: _fetchCat(),
builder: (context, snapshot) => Text(
snapshot.hasData ? snapshot.data ?? 'NO CATEGORY' : 'Loading...'),
);
}
}

How to display an alert message if there is no data flutter

I am doing a query about the data from a mysql database and they are displayed in a list.
My problem is if there is no data in the database. I would like to display an alert message stating that or display a text to warn that there is no available data.
This is the code page that I use to display the data:
void main() {
runApp(Your()
);
}
class Your extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<Your> {
MyPreferences _myPreferences = MyPreferences();
String apiURL;
var id;
List<Flowerdata> _flowersDataForTheListView = List<Flowerdata>();
Future<List<Flowerdata>> fetchNotes() async {
id=_myPreferences.id;
final String url = 'https://=============/All.php?id=' + id.toString();
var response = await http.get(url);
var flowers = List<Flowerdata>();
if (response.statusCode == 200) {
var flowersJsonData = json.decode(response.body);
for (var flower in flowersJsonData) {
flowers.add(Flowerdata.fromJson(flower));
}
}
else{
}
return flowers;
}
#override
void initState() {
fetchNotes();
super.initState();
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return _listItem(index);
},
);
}
_listItem(index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: [
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text( _flowersDataForTheListView[index].Name,style: TextStyle(fontSize: 17)),
SizedBox(height: 5.0,),
],
),
),
],
),
],
);
}
}
PHP contact page:
<?php
include 'connt.php';
$id=$_GET['id'];
$sql = "SELECT * FROM user WHERE id=? ORDER BY id DESC";
$stmt = $con->prepare($sql);
$stmt->bind_param("s",$id);
$stmt->execute();
$result = $stmt->get_result();
//$result = $con->query($sql);
if ($result->num_rows >0) {
while($row[] = $result->fetch_assoc()) {
$item = $row;
$json = json_encode($item, JSON_NUMERIC_CHECK);
}
} else {
echo "No";
}
echo $json;
$con->close();
?>
How can I do this if someone knows please help me.
You don't seem to be using MaterialApp in your flutter code, but if you use it, Scaffold widget has a method called showSnackbar where you can display a message that pops up at the bottom of the page.
Here is a sample code for showSnackbar:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: MyList(),
);
}
}
class MyList extends StatefulWidget {
#override
_MyListState createState() => _MyListState();
}
class _MyListState extends State<MyList> {
#override
Widget build(BuildContext context) {
return Container();
}
#override
void initState() {
fetchNotes();
super.initState();
}
Future<void> fetchNotes() async {
// Here, you actually fetch the data from API
await Future.delayed(Duration(seconds: 3));
final flowers = [];
if (flowers.isEmpty) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Data is empty'),
),
);
}
}
}

How to call method at App start if I am using provider?

I want to complete get request to server to get data for my app at it start. I read several topics that describe how to run method after building widgets. But all of them are describe situations when provider is not using. And I am not sure that it's good idea to do this request inside widget.
I tried several approaches but did not get success.
Here is my code:
void main() async {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider<TenderApiData>(
builder: (_) => TenderApiData(), child: HomePage()),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(), body: MainContainer());
}
}
class TenderApiData with ChangeNotifier {
String access_token;
List<Map<String, String>> id_names;
String access_token_url = "https://...";
getApiKey() async { // I need to call this method at app start up
var response = await http
.post(access_token_url, headers: {"Accept": "application/json"});
if (response.statusCode == 200) {
access_token = json.decode(response.body)['access_token'];
notifyListeners();
}
}
}
class MyTestWidget extends StatefulWidget {
MyTestWidgetState createState() => MyTestWidgetState();
}
class MyTestWidgetState extends State<MyTestWidget> {
bool isKeyGetted = false;
// before I used this when I extracted data on click event.
// I am not sure that it's needed now
#override
void didChangeDependencies() {
if (!isKeyGetted) {
Provider.of<TenderApiData>(context).getApiKey();
isKeyGetted = !isKeyGetted;
}
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
if (!isKeyGetted) {
Provider.of<TenderApiData>(context).getApiKey();
isKeyGetted = !isKeyGetted;
}
var result = Provider.of<TenderApiData>(context).access_token;
var test = Provider.of<TenderApiData>(context).id_names;
return Column(
children: <Widget>[
RaisedButton(
onPressed: Provider.of<TenderApiData>(context).getRegionsList,
child: Text("get regions"),
),
],
);
}
}
class MainContainer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Table(
children: [
TableRow(children: [
Row(
children: <Widget>[
Container(child: MyTestWidget()),
Container(child: Text("Regions"),),
Expanded(child: SelectRegions(), )
],
)
]),
TableRow(children: [
Row(
children: <Widget>[
Text("Label"),
Text("Value"),
],
)
]),
],
);
}
}
You can store TenderApiData as member of MyApp, make a startup call in MyApp constructor and pass existing instance to descendants.
Here is how it will look:
class MyApp extends StatelessWidget {
final TenderApiData _tenderApiData = TenderApiData();
MyApp() {
_tenderApiData.getApiKey();
};
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider<TenderApiData>(
builder: (_) => _tenderApiData, child: HomePage()),
);
}
}
Other classes will stay unchanged.
Another option would be to pass TenderApiData as constructor parameter into MyApp, making it more testable.
void main() {
final TenderApiData tenderApiData = TenderApiData();
tenderApiData.getApiKey(); // call it here or in MyApp constructor - now it can be mocked and tested
runApp(MyApp(tenderApiData));
}
class MyApp extends StatelessWidget {
final TenderApiData _tenderApiData;
MyApp(this._tenderApiData);
// ...
You can add a constructor on your TenderApiData do trigger custom logic:
class TenderApiData with ChangeNotifier {
TenderApiData() {
// TODO: call `getApiKey`
}
}
You can use FutureProvider value.
Separate method api to service (my_service.dart):
class MyService {
Future<String> getApiKey() async {
// I need to call this method at app start up
var response = await http
.post(access_token_url, headers: {"Accept": "application/json"});
if (response.statusCode == 200) {
return json.decode(response.body)['access_token'];
}
}
}
And than call from MyApp
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureProvider<String>.value(
value: MyService().getApiKey(),
child: HomePage(),
);
}
}
In HomePage:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
String token = Provider.of<String>(context);
return Scaffold();
}
}
You could also just use a boolean flag to run the method only once.
Provider
import 'screen.abstract.dart';
class MyProvider with ChangeNotifier {
_hasInitialized = false;
// This will only be run once, when called
fetchApiOnce() {
if (_hasInitialized) {
return;
}
_hasInitialized = true;
/* DO STUFF */
}
}