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'));
}
}
}
Related
I want to get the bool of a shared pref to decide which Widget should get loaded, but the method cant be async or to bool cant get the value because it is not allowed to "await" the value. I have tried fixing it, but it mostly fails because "home" can't receive a future widget..., is there another way how I could do this?
void main() => runApp(MyApp());
setloginbool() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool("savelogin", true);
}
Future<bool> getloginbool() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool savelogin = prefs.getBool("savelogin") ?? false;
return savelogin;
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'KHS Plan',
theme: (ThemeData(
textTheme: const TextTheme(
bodyText1: TextStyle(fontSize: 14)
)
)),
home: checkifpassword(),
);
}
}
Widget checkifpassword() {
bool s = await getloginbool();
if(s){
return const Login();
} else {
return const MyHomePage();
}
}
//This does not work as well
checkifpassword() async {
bool s = await getloginbool();
if(s){
return const Login();
} else {
return const MyHomePage();
}
}
You can use FutureBuilder on Home
Future<bool> checkifpassword() async {
//perfrom your async operation and return bool
return await Future.delayed(Duration(seconds: 2), () {
return true;
});
}
And home
home: FutureBuilder<bool>(
future: checkifpassword(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data!) {// for true
return Login();;
} else return MyHomePage();
}
/// check others state
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
},
)
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 am trying to check the login of the user. But, checklogin() even on returning null doesn't equate to null in the if condition.
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
if(checkLogin() == null) {
return Login();
} else {
return Dashboard();
}
}
Future<String> checkLogin() async {
var prefs = await SharedPreferences.getInstance();
var key = 'Token';
var value = prefs.getString(key);
print(value);
return value;
}
}
I just used a future builder which manages the data returned from future through AsyncSnapshot.
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: checkLogin(),
builder: (BuildContext context, AsyncSnapshot snapshot){
if (snapshot.hasData){
var value = snapshot.data;
if(value == null){
return Login();
}else{
return Dashboard();
}
}else return Dashboard();
}
);
}
Future<String> checkLogin() async {
var prefs = await SharedPreferences.getInstance();
var key = 'Token';
var value = prefs.getString(key);
print(value);
return value;
}
}
The issue is checkLogin function is async and return Future for which you'll have wait and you wait in build directly.
So, here is a better and correct implementation.
``
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool isLogin = false;
#override
void initState() {
checkLogin();
super.initState();
}
checkLogin() async {
var prefs = await SharedPreferences.getInstance();
var key = 'Token';
var value = prefs.getString(key) ?? false;
setState(() {isLogin = value;});
}
#override
Widget build(BuildContext context) {
return isLogin ? Login() : Dashboard();
}
}
``
I need to convert Future <bool> to bool. I know it can be done with then and await but how?
class MyHomeApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
bool isLogin;
checkDatabase().then((onValue){
isLogin = onValue;
});
if(isLogin) return HomePageScreen();
if(!isLogin) return SignInScreen();
}
Future<bool> checkDatabase() async{
Directory directory = await getApplicationDocumentsDirectory();
String path = directory.path + 'koca.db';
return databaseExists(path);
}
}
You could use a FutureBuilder :
class MyHomeApp extends StatelessWidget {
Future<bool> get checkDatabase async {
Directory directory = await getApplicationDocumentsDirectory();
String path = directory.path + 'koca.db';
return databaseExists(path);
}
#override
Widget build(BuildContext context) =>
FutureBuilder(
future: checkDatabase,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
if (snapshot.data) {
return HomePageScreen();
} else {
return SignInScreen();
}
}
}
return Center(child: CircularProgressIndicator(),);
},
);
}
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;
}
}