I have a flutter screen called TestMain which has a scaffold and white background. The scaffolds body is supposed to change if certain events happen. The events are stored as a boolean. There is "isLocked" and "isPaused" which get emitted by a Riverpod Stream Provider and "isCheating" which changes when Applifecyle events get triggered. All of the three booleans are stored as Riverpod StateProviders, because of its global accesibility.
This is is my "isCheatingProvider":
final isCheatingProvider = StateProvider.autoDispose<bool>((ref) => false);
The "isPausedProvider" and "isLockedProvider" are the same.
This is the TestMain screen
class TestMain extends ConsumerStatefulWidget {
const TestMain({super.key});
#override
ConsumerState<TestMain> createState() => _TestMainScreenState();
}
class _TestMainScreenState extends ConsumerState<TestMain>
with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
final isCheating = ref.watch(isCheatingProvider.notifier);
switch (state) {
case AppLifecycleState.resumed:
case AppLifecycleState.inactive:
await sendCheatingAttempt(ref);
setState(() {
isCheating.state = true;
});
break;
case AppLifecycleState.paused:
await sendCheatingAttempt(ref);
setState(() {
isCheating.state = true;
});
break;
case AppLifecycleState.detached:
await sendCheatingAttempt(ref);
setState(() {
isCheating.state = true;
});
break;
}
}
#override
Widget build(BuildContext context) {
final List<Item> items = ref.watch(itemsProvider);
final AsyncValue<dynamic> wsTestListenerMessage =
ref.watch(testListenerProvider);
final isLocked = ref.watch(isLockedProvider.notifier);
final isPaused = ref.watch(isPausedProvider.notifier);
final isCheating = ref.watch(isCheatingProvider.notifier);
wsTestListenerMessage.when(
loading: () => {},
error: (err, stack) => print('Test State Error: $err'),
data: (message) async {
Future.delayed(const Duration(seconds: 0), () {
if (message["lock"] == true) {
isLocked.state = true;
}
if (message["unlock"] == true) {
isLocked.state = false;
}
if (message["paused"] == true) {
isPaused.state = true;
}
if (message["resumed"] == true) {
isPaused.state = false;
}
});
},
);
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: isPaused.state
? const ErrorOverlay(text: 'paused')
: isLocked.state || isCheating.state
? const ErrorOverlay(text: 'cheating')
: const TestView()),
);
}
}
But it doesnt work. No matter what I do. I added the Future.delayed(const Duration(seconds: 0), () {} around the if-statements, because it complained about changing the provider in build method, I use setState() in didChangeAppLifecycleState(), but can't use it in the listener, because the listener would called over and over again. It shouldnt be openend more than once.
(ErrorOverlay is a custom widget that just shows the text in big red letters, in the center)
remove the setState, this will do nothing
for set a state use ref.read(provider.notifier).state
for watch use ref.watch(isCheatingProvider)
By changing all that it is good by testing on my side :
final isCheatingProvider = StateProvider.autoDispose<bool>((ref) => false);
class TestMain extends ConsumerStatefulWidget {
const TestMain({key});
#override
ConsumerState<TestMain> createState() => _TestMainScreenState();
}
class _TestMainScreenState extends ConsumerState<TestMain>
with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
final isCheating = ref.read(isCheatingProvider.notifier);
switch (state) {
case AppLifecycleState.resumed:
case AppLifecycleState.inactive:
isCheating.state = true;
break;
case AppLifecycleState.paused:
isCheating.state = true;
break;
case AppLifecycleState.detached:
isCheating.state = true;
break;
}
}
#override
Widget build(BuildContext context) {
final isCheating = ref.watch(isCheatingProvider);
return Scaffold(
backgroundColor: isCheating ? Colors.red : Colors.white,
body: SafeArea(
child: isCheating ? Text('cheat') : Text(' good')
)
);
}
}
You are incorrectly using StateProvider. To watch StateNotifier you should use
final isCheating = ref.watch(isCheatingProvider);
and to change provider use
ref.read(productSortTypeProvider.notifier).state = value;
So you have to change all provider related code.
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
final isCheatingNotifier = ref.read(isCheatingProvider.notifier);
switch (state) {
case AppLifecycleState.resumed:
case AppLifecycleState.inactive:
await sendCheatingAttempt(ref);
isCheatingNotifier.state = true;
break;
case AppLifecycleState.paused:
await sendCheatingAttempt(ref);
isCheatingNotifier.state = true;
break;
case AppLifecycleState.detached:
await sendCheatingAttempt(ref);
isCheatingNotifier.state = true;
break;
}
}
#override
Widget build(BuildContext context) {
final List<Item> items = ref.watch(itemsProvider);
final AsyncValue<dynamic> wsTestListenerMessage =
ref.watch(testListenerProvider);
final isLocked = ref.watch(isLockedProvider);
final isPaused = ref.watch(isPausedProvider);
final isCheating = ref.watch(isCheatingProvider);
wsTestListenerMessage.when(
loading: () => {},
error: (err, stack) => print('Test State Error: $err'),
data: (message) async {
Future.delayed(const Duration(seconds: 0), () {
final isLockedNotifier = ref.read(isLockedProvider.notifier);
final isPausedNotifier = ref.read(isPausedProvider.notifier);
if (message["lock"] == true) {
isLockedNotifier.state = true;
}
if (message["unlock"] == true) {
isLockedNotifier.state = false;
}
if (message["paused"] == true) {
isPausedNotifier.state = true;
}
if (message["resumed"] == true) {
isPausedNotifier.state = false;
}
});
},
);
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: isPaused
? const ErrorOverlay(text: 'paused')
: isLocked || isCheating
? const ErrorOverlay(text: 'cheating')
: const TestView()),
);
}
Related
Hey i make a Favorite System with a bool to say if is favorite or not.
But if the name of the bool is always the same it applies to all my entries!
but each entry has its own name (widget.name), and i thought maybe something like that could work
bool widget.name;
but this not work :(
how can i solve that each entry has its own bool?
by the way i use this plugin for that
https://pub.dev/packages/shared_preferences/example
SharedPreferences sharedPreferences;
bool isfavorit;
#override
void initState() {
super.initState();
SharedPreferences.getInstance().then((SharedPreferences sp) {
sharedPreferences = sp;
isfavorit = sharedPreferences.getBool(spKey);
// will be null if never previously saved
if (isfavorit == null) {
isfavorit = false;
persist(isfavorit); // set an initial value
}
setState(() {});
});
}
void persist(bool value) {
setState(() {
isfavorit = value;
});
sharedPreferences?.setBool(spKey, value);
}
Complete Code
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Details extends StatefulWidget {
final String name;
Details(
this.name,
);
#override
_DetailsState createState() => _DetailsState();
}
const String spKey = 'myBool';
class _DetailsState extends State<Details> {
SharedPreferences sharedPreferences;
bool isfavorit;
#override
void initState() {
super.initState();
SharedPreferences.getInstance().then((SharedPreferences sp) {
sharedPreferences = sp;
isfavorit = sharedPreferences.getBool(spKey);
// will be null if never previously saved
if (isfavorit == null) {
isfavorit = false;
persist(isfavorit); // set an initial value
}
setState(() {});
});
}
void persist(bool value) {
setState(() {
isfavorit = value;
});
sharedPreferences?.setBool(spKey, value);
}
// ignore: missing_return
IconData favicon() {
if (isfavorit == true) {
return Icons.favorite;
} else if (isfavorit == false) {
return Icons.favorite_border;
}
}
// ignore: missing_return
Color favicolor() {
if (isfavorit == true) {
return Colors.red;
} else if (isfavorit == false) {
return Colors.white;
}
}
void changefav() {
if (isfavorit == true) {
return persist(false);
} else if (isfavorit == false) {
return persist(true);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(
favicon(),
color: favicolor(),
),
onPressed: () => changefav(),
),
],
title: Text(widget.name),
),
body: Container(
child: Text(widget.name),
),
);
}
}
You are always saving the isFavorite to the same key in shared preferences, instead of using a constant key use one that is based on the widget.name
So for instance:
sharedPreferences.getBool('details_favorite_${widget.name}');
So I have a listview with which I used Future to fetch data and it displays fine. Now am trying to parse the value on the clicked item from the listview page to another page that will show details of the item click. Please how do I achieve this?
The Future
List dealData = List();
Future<String> _fetchComment() async {
setState(() {
isLoading = true;
debugPrint("emirate state");
});
try {
debugPrint("emirate try");
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
print('connected');
debugPrint("emirate connect");
String url;
debugPrint("my select:$_mySelection");
if (_mySelection == null && _myFeatureSelection == null) {
url = "my rest api";
} else if (_myFeatureSelection != null) {
url =
"my rest api";
_mySelection = null;
} else if (_mySelection != null && _myFeatureSelection == null) {
url = "my rest api";
}
print("our url:$url");
var res = await http
.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
var resBody = json.decode(res.body);
debugPrint("emirate url:$url");
setState(() {
dealData = resBody;
isLoading = false;
});
print(resBody);
debugPrint("emirate:$resBody");
return "Sucess";
} else {
throw Exception('Failed to load profile');
}
} on SocketException catch (_) {
print('not connected');
setState(() => isLoading = false);
Navigator.popUntil(
context, (_) => !Navigator.canPop(context));
Navigator.pushReplacement(
context,
new MaterialPageRoute(
builder: (BuildContext context) => NoInternet()));
}
}
My listview and onclick
dealData
.map(
(position) => FutureBuilder<String>(
future: getDistance(
position["lat"],
position["lng"])
.then((value) =>
value.toString()),
builder: (context, snapshot) {
double myrate = double.parse(
position["ratings"] ==
null
? "0"
: position["ratings"]);
return Container(
child:Card(child:
GestureDetector(
onTap: () {
print(position); // position printed here
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext ctx) => Maps(position)));
},
).toList(),
My Map Class
class mapsFinal extends StatefulWidget {
final int position;
const mapsFinal(this.position);
#override
_MapsState createState() => _MapsState ();
}
class _MapsState extends State<mapsFinal> {
Widget build(BuildContext context) {
return Text("title" + widget.position.toString());
}
}
Please I need a second page that will display the item I clicked on here.
This is the simplest example of passing a value to a widget called "Maps":
// BOILERPLATE CODE TO MAKE THE EXAMPLE RUN
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Maps("THE VALUE"),
),
),
);
}
}
// THIS IS THE CLASS YOU NEED TO LOOK AT:
class Maps extends StatefulWidget {
final String position;
const Maps(this.position);
#override
_MapsState createState() => _MapsState ();
}
class _MapsState extends State<Maps> {
Widget build(BuildContext context) {
return Text("You passed: " + widget.position);
}
}
I have a screen from which I want to navigate to a new screen if a provided boolean value(isLoggedIn) gets changed to true. The first build method below is not possible since Navigator cannot be called during build. I attempted to do this in build because I need access to context to use my provider. The value of isLoggedIn coming from the provider of Authorization can potentially change at any time, so I would have to check for this.
How can I solve this using provider and navigator?
//Not working solution
Widget build(BuildContext context) {
Authorization auth = Provider.of<Authorization>(context);
return Scaffold(
body: Center(
child: auth.isLoggedIn
? Text(
"Logged In",
)
: Navigator.pushReplacementNamed(context, 'sign-in')),
);
}
I can do it without Navigator like this:
//Working solution
class SplashScreen extends StatelessWidget {
static const routeName = 'splash';
#override
Widget build(BuildContext context) {
final auth = Provider.of<Authorization>(context);
return auth.isLoggedIn ? HomeScreen() : SignInScreen();
}
}
But I do not now if that is a solid approach.
Here is an approach to solving your problem.
enum AuthStatus {
NOT_DETERMINED,
NOT_LOGGED_IN,
LOGGED_IN,
}//Always define this outside the class.
AuthStatus authStatus = AuthStatus.NOT_DETERMINED;
#override
void initState() {
super.initState();
getCurrentUser().then((user) {
setState(() {
if (user != null) {
_userId = user?.uid;
}
authStatus =
user?.uid == null ? AuthStatus.NOT_LOGGED_IN : AuthStatus.LOGGED_IN;
});
});
}
Future<FirebaseUser> getCurrentUser() async {
FirebaseUser user = await _firebaseAuth.currentUser();
return user;
}
void loginCallback() {
getCurrentUser().then((user) {
setState(() {
_userId = user.uid.toString();
authStatus = AuthStatus.LOGGED_IN;
Navigator.of(context).pushReplacementNamed('/');
});
});
}
Widget buildWaitingScreen() {
return Scaffold(
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: CircleAvatar(
backgroundColor: Colors.transparent,
radius: 70.0,
child: Image.asset('assets/icons/icon.png'),
),
),
);
}
#override
Widget build(BuildContext context) {
switch (authStatus) {
case AuthStatus.NOT_DETERMINED:
return buildWaitingScreen();
break;
case AuthStatus.NOT_LOGGED_IN:
return new LoginSignUpPage();
break;
case AuthStatus.LOGGED_IN:
if (_userId.length > 0 && _userId != null) {
return new HomePage();
} else
return buildWaitingScreen();
break;
default:
return buildWaitingScreen();
}
}
}
To navigate use this in initState
void loginCallback() {
getCurrentUser().then((user) {
setState(() {
_userId = user.uid.toString();
authStatus = AuthStatus.LOGGED_IN;
});});
if(authStatus==AuthStatus.LOGGED_IN){
Navigator.pushReplacementNamed(context, 'sign-in')} }
This program works okay on first build.When I disconnect my device and reconnects it,it is showing, bad state:stream has already been listened to,
Probably error is generated by stream that listening to Bluetooth characteristic.Whats the work around?
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'package:vibration/vibration.dart';
StreamSubscription _scanSubscription;
StreamSubscription _deviceConnection;
Stream<List<int>> stream;
List<double> traceDust = List();
const String CHAR_UUID = "AA:48:F8:CC:07:12";
const String Device_Name = "myDevice";
const String CHARACTERISTIC_UUID = "00000000-0111-1000-4000-000000000000";
BluetoothDeviceState _state;
Map<DeviceIdentifier, ScanResult> scanResults = new Map();
List<BluetoothService> services = new List();
BluetoothCharacteristic characteristic;
FlutterBlue flutterBlue = FlutterBlue.instance;
BluetoothDevice device;
class SearchScreen extends StatefulWidget {
#override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
#override
void initState() {
super.initState();
_startScan();
}
#override
void dispose() {
super.dispose();
_stopScan();
_deviceConnection?.cancel();
_deviceConnection = null;
device.disconnect();
}
_startScan() {
_scanSubscription =
flutterBlue.scan(timeout: Duration(seconds: 4)).listen((scanResult) {
if (CHAR_UUID == scanResult.device.id.toString()) {
_stopScan();
_connect(scanResult.device);
print('connected');
}
}, onDone: _stopScan());
}
_stopScan() {
_scanSubscription?.cancel();
_scanSubscription = null;
}
_connect(BluetoothDevice d) async {
device = d;
await device.connect(autoConnect: true);
await device.discoverServices().then((value) {
setState(() {
services = value;
});
});
_turnOnCharacterService(services);
}
_turnOnCharacterService(List<BluetoothService> ser) async {
ser.forEach((service) {
service.characteristics.forEach((character) {
if (character.uuid.toString() == CHARACTERISTIC_UUID) {
character.setNotifyValue(!character.isNotifying);
setState(() {
stream = character.value;
});
}
});
});
}
String _dataParser(List<int> dataFromDevice) {
return utf8.decode(dataFromDevice);
}
vibrateOnAlert() async {
if (await Vibration.hasVibrator()) {
Vibration.vibrate(duration: 1000);
}
}
#override
Widget build(BuildContext context) {
return Container(
child: StreamBuilder<BluetoothDeviceState>(
stream: device.state,
initialData: BluetoothDeviceState.connecting,
builder: (context, snapshot) {
if (snapshot.data == BluetoothDeviceState.connected) {
return StreamBuilder<List<int>>(
stream: stream,
builder: (context, snapshot) {
var currentValue;
if (snapshot.hasError) {
return Text('Error');
}
if (snapshot.connectionState == ConnectionState.active) {
currentValue = _dataParser(snapshot.data);
traceDust.add(double.tryParse(currentValue) ?? 0);
if (currentValue.toString().compareTo('vibrate') == 0) {
vibrateOnAlert();
}
} else {
return Text('disconnected');
}
print('$currentValue');
return Text('connected');
});
}
return FlatButton(
color: Colors.white,
child: Text('reconnecct'),
onPressed: () {
setState(() {
flutterBlue.startScan(timeout: Duration(seconds: 2));
});
},
);
},
));
}
}
PS: Here flat button does nothing.Since connection state is a streambuilder it automatically reconnects and shows error.
in my simple code as new screen, unfortunately FutureBuilder work and get data from method twice!!
i'm not sure whats problem and how can i avoid that
class LessonDetail extends StatefulWidget {
final String monthKey;
final String lessonFileKey;
LessonDetail({#required this.monthKey, #required this.lessonFileKey});
#override
State<StatefulWidget> createState() {
return _LessonDetailState(monthKey, lessonFileKey);
}
}
class _LessonDetailState extends BaseState<LessonDetail> {
String monthKey;
String lessonFileKey;
_LessonDetailState(this.monthKey, this.lessonFileKey);
#override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
body: FutureBuilder(
future: _getLessonDetail(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
PlayLessonResponse response = snapshot.data;
print(response);
}
return Center(
child: CircularProgressIndicator(),
);
}),
),
);
}
Future<PlayLessonResponse> _getLessonDetail() async {
AudioList audioList = AudioList(
'http://www.sample.com',
'aaaaa'
);
List<AudioList> lst = [audioList,audioList,audioList];
PlayLessonResponse response = PlayLessonResponse(
2,
'',
'http://www.sample.com',
'2',
lst,
1,
'ssss'
);
print('++++++++++++++++++++');
return response;
}
}
BaseState class content:
abstract class BaseState<T extends StatefulWidget> extends State {
final Connectivity _connectivity = Connectivity();
StreamSubscription<ConnectivityResult> _connectivitySubscription;
bool isOnline = true;
Future<void> initConnectivity() async {
try {
await _connectivity.checkConnectivity();
} on PlatformException catch (e) {
print(e.toString());
}
if (!mounted) {
return;
}
await _updateConnectionStatus().then((bool isConnected){
if(mounted){
setState(() {
isOnline = isConnected;
});
}
});
}
#override
void initState() {
super.initState();
initConnectivity();
_connectivitySubscription = Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) async {
await _updateConnectionStatus().then((bool isConnected){
if(mounted){
setState(() {
isOnline = isConnected;
});
}
});
});
}
#override
void dispose() {
_connectivitySubscription.cancel();
super.dispose();
}
Future<bool> _updateConnectionStatus() async {
bool isConnected;
try {
final List<InternetAddress> result =
await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
isConnected = true;
}
} on SocketException catch (_) {
isConnected = false;
return false;
}
return isConnected;
}
}
output:
I/flutter (32289): ++++++++++++++++++++
I/flutter (32289): ++++++++++++++++++++
Just like what #Ricardo said, you shouldn't call the function directly inside the FutureBuilder's future method.
Instead, you should 1st run your function in init state, and store the response in a new variable. Only then assign variable to the future of FutureBuilder.
Code Example:
class LessonDetail extends StatefulWidget {
final String monthKey;
final String lessonFileKey;
LessonDetail({#required this.monthKey, #required this.lessonFileKey});
#override
State<StatefulWidget> createState() {
return _LessonDetailState(monthKey, lessonFileKey);
}
}
class _LessonDetailState extends BaseState<LessonDetail> {
String monthKey;
String lessonFileKey;
Future<PlayLesssonResponse> _myResponse; //added this line
_LessonDetailState(this.monthKey, this.lessonFileKey);
#override
void initState() {
_myResponse = _getLessonDetail(); // added this line
super.initState();
}
#override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
body: FutureBuilder(
future: _myResponse, //use _myResponse variable here
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
PlayLessonResponse response = snapshot.data;
print(response);
}
return Center(
child: CircularProgressIndicator(),
);
}),
),
);
}
Future<PlayLessonResponse> _getLessonDetail() async {
AudioList audioList = AudioList(
'http://www.sample.com',
'aaaaa'
);
List<AudioList> lst = [audioList,audioList,audioList];
PlayLessonResponse response = PlayLessonResponse(
2,
'',
'http://www.sample.com',
'2',
lst,
1,
'ssss'
);
print('++++++++++++++++++++');
return response;
}
}