Flutter avoid multiple running FutureBuilder - flutter

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;
}
}

Related

Flutter prevent building a new widget if already in tree

For some reason, the Build method is called twice which results in two MainContent widgets being created. The problem is that in one of my widgets a Listener displays messages to the user according to certain actions. Because Maincontent is duplicated, messages are displayed twice.
widget tree
How to prevent the MainContent widget from being duplicated?
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Bloc.observer = ProductBlocObserver();
var productStorage = ProductStorage();
await productStorage.products().then((localProducts) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('isMember', false);
await prefs.setBool('membershipError', false);
await prefs.setBool('firstOpen', true);
await prefs.setString('memberFirstName', '');
await prefs.setString('token', '');
runApp(App(list: localProducts));
});
}
class App extends StatelessWidget {
List<LocalProductEntity> list;
App({Key? key, required this.list}) : super(key: key) {
list = list;
}
#override
Widget build(BuildContext context) {
return BlocProvider <ListBloc>(
create: (_) => ListBloc(list: list),
child: MaterialApp(
debugShowCheckedModeBanner: true,
title: constants.appTitle,
theme: ThemeData(
colorScheme: ColorScheme.fromSwatch().copyWith(
primary: Color(ColorsLNC.green5),
secondary: Color(ColorsLNC.green1),
),
),
home: BlocPage()
),
);
}
}
class BlocPage extends StatelessWidget {
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
late Future<bool> isMember;
late Future<bool> membershipError;
late Future<bool> firstOpen;
late Future<String> memberFirstName;
late Future<String> token;
late BuildContext context;
BlocPage({super.key});
#override
Widget build(BuildContext context) {
this.context = context;
isMember = _prefs.then((SharedPreferences prefs) {
return prefs.getBool('isMember') ?? false;
});
membershipError = _prefs.then((SharedPreferences prefs) {
return prefs.getBool('membershipError') ?? false;
});
firstOpen = _prefs.then((SharedPreferences prefs) {
return prefs.getBool('firstOpen') ?? false;
});
memberFirstName = _prefs.then((SharedPreferences prefs) {
return prefs.getString('memberFirstName') ?? '';
});
token = _prefs.then((SharedPreferences prefs) {
return prefs.getString('token') ?? '';
});
return FutureBuilder(
future: membershipFlow(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const CircularProgressIndicator();
default:
return snapshot.data;
}
});
}
Future<Widget> membershipFlow() async {
if (await isMember == true && await membershipError == false) {
return SplashPage(
mainContext : context,
goToPage: MainContent(),
text: await memberFirstName,
duration: 2,
);
} else if (await isMember == false && await membershipError == false) {
return const MembershipForm();
} else if (await isMember == false && await membershipError == true) {
return ErrorPage(text: Babel.translate(key: 'E_WRONG_MEMBERSHIP'));
} else {
return ErrorPage(text: Babel.translate(key: 'E_UNEXPECTED'));
}
}
}
Use const with both constructor and while using it.
For example
const BlocPage({super.key}) and const BlocPage().
You code has different issue tough. You could refactor your code like this and build won't be called repeatedly.
class BlocPage extends StatelessWidget {
const BlocPage({super.key});
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: membershipFlow(context),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const CircularProgressIndicator();
default:
return snapshot.data;
}
});
}
Future<Widget> membershipFlow(BuildContext context) async {
final prefs = await SharedPreferences.getInstance();
final isMember = prefs.getBool('isMember') ?? false;
final membershipError = prefs.getBool('membershipError') ?? false;
final firstOpen = prefs.getBool('firstOpen') ?? false;
final memberFirstName = prefs.getString('memberFirstName') ?? '';
final token = prefs.getString('token') ?? '';
if (isMember == true && membershipError == false) {
return SplashPage(
mainContext : context,
goToPage: MainContent(),
text: memberFirstName,
duration: 2,
);
} else if (isMember == false && membershipError == false) {
return const MembershipForm();
} else if (isMember == false && membershipError == true) {
return ErrorPage(text: Babel.translate(key: 'E_WRONG_MEMBERSHIP'));
} else {
return ErrorPage(text: Babel.translate(key: 'E_UNEXPECTED'));
}
}
}

convert future builder to listview builder

i want to fetch data withour using future, can someone help me to convert it ? direct using listview.builder without using future builder. and how can i post it ? i already try it for a couple days and stuck here. please explain it too
thank you
import 'package:flutter/material.dart';
import 'package:flutter/src/foundation/key.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:latihan_dio/src/features/home/domain/user.dart';
import '../../../../dio_client.dart';
class myHomepage extends StatefulWidget {
const myHomepage({Key? key}) : super(key: key);
#override
State<myHomepage> createState() => _myHomepageState();
}
class _myHomepageState extends State<myHomepage> {
// List<User> users = [];
var selectedIndex = 0;
#override
void initState() {
super.initState();
// fetchData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder<List<User>>(
future: fetchData(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
// if (snapshot.hasError) {
// return Text('Error: ${snapshot.error}');
// } else {
List<User>? data = snapshot.data;
return ListView.builder(
itemBuilder: (context, index) {
return Column(children: [
Text(data![index].firstName!),
]);
},
itemCount: data?.length,
);
}
}
// },
),
));
}
Future<List<User>> fetchData() async {
var Response = await DioClient().apiCall(
url: 'https://reqres.in/api/users?page=2',
requestType: RequestType.GET,
// queryParameters: {},
);
if (Response.statusCode == 200) {
List<dynamic> listUser = Response.data['data'];
List<User> users = listUser.map((e) => User.fromJson(e)).toList();
return users;
} else {
return [];
}
}
}
// Future<void> fetchData() async {
// var Response = await DioClient().apiCall(
// url: 'https://reqres.in/api/users?page=2',
// requestType: RequestType.GET,
// // queryParameters: {},
// );
// // List<dynamic> listUser = Response.data;
// // OR
// List<dynamic> listUser =
// Response.data['data']; // if you want to access data inside it
// List<User> users = listUser.map((e) => User.fromJson(e)).toList();
// }
as u can see here is my homepage. i make a freeze class and using dio client here.
Try this
class _myHomepageState extends State<myHomepage> {
List<User> user = [];
bool isLoading = false;
#override
void initState() {
initFunction();
super.initState();
}
void initFunction() async {
setState((){
isLoading= true;
})
user = await fetchData();
setState((){
isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: isLoading
? CircularProgressIndicator()
: ListView.builder(
itemBuilder: (context, index) {
return Column(children: [
Text(user[index].firstName!),
]);
},
itemCount: user.length,
);
),
));
}
Future<List<User>> fetchData() async {
var Response = await DioClient().apiCall(
url: 'https://reqres.in/api/users?page=2',
requestType: RequestType.GET,
// queryParameters: {},
);
if (Response.statusCode == 200) {
List<dynamic> listUser = Response.data['data'];
List<User> users = listUser.map((e) => User.fromJson(e)).toList();
return users;
} else {
return [];
}
}
}

How to complete async operation in initState and update ui?

The longRequest() completes with a code 200, but ui displays 400 when the longRequest() is completed. What's wrong with this scenario?
class Parsit extends StatefulWidget {
#override
_ParsitState createState() => _ParsitState();
}
class _ParsitState extends State<Parsit> {
int code = 400;
#override
void initState() {
super.initState();
setState(() {
longRequest().then((value) => code = value);
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Center(
child: Text('$code'),
),
);
}
Future<int> longRequest() async {
final response = await Requests.get('http://google.com');
response.raiseForStatus();
...
print(response.statusCode); // 200
return response.statusCode;
}
}
you can with a boolean value check longRequest() is finished and for UI put a loading or something :
class Parsit extends StatefulWidget {
#override
_ParsitState createState() => _ParsitState();
}
class _ParsitState extends State<Parsit> {
int code = 400;
bool longRequestIsFinish = false
#override
void initState() {
super.initState();
longRequest().then((value) {
setState(() {
code = value;
longRequestIsFinish = true;
});
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Center(
child: longRequestIsFinish ? Text('$code') : LoadingWidget(),
),
);
}
Future<int> longRequest() async {
final response = await Requests.get('http://google.com');
response.raiseForStatus();
...
print(response.statusCode); // 200
return response.statusCode;
}
}
using FutureBuilder widget
class Parsit extends StatefulWidget {
#override
_ParsitState createState() => _ParsitState();
}
class _ParsitState extends State<Parsit> {
Future<int> code;
#override
void initState() {
super.initState();
code = longRequest();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Center(
child: FutureBuilder<int>(
future: code,
builder: (context, snaopshot) {
if (snapshot.hasData)
return Text(snapshot.data.toString());
return CircularProgressIndicator();
),
);
}
Future<int> longRequest() async {
final response = await Requests.get('http://google.com');
response.raiseForStatus();
...
print(response.statusCode); // 200
return response.statusCode;
}
}

How to get value from map on another page in flutter

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);
}
}

Stop listening to a stream

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.