Button pressed return a future builder - flutter

I have a button and if pressed should return a future builder here is my code.
I already search some examples on the web but no luck, Im new in flutter development and trying to create a simple login with api call.
Future<AccessToken>fetchAccessToken() async{final token = await _repository.fetchToKen();
>>return token;
}
onPressed: () {FutureBuilder<AccessToken>(future:bloc.fetchAccessToken(),builder: (context, snapshot) {if (snapshot.hasError) {return Text('Error');} else if (snapshot.hasData) {return Text('data');} else {return `Center`(child: CircularProgressIndicator(),);}},);}
I want to show a progress indicator while waiting for the api response, but after I receive the response, my builder inside the future builder is not called.

You can't simply return a widget and place it in the widget tree like that. Maybe you can use conditional list for hiding and showing the FutureBuilder widget.
import 'package:flutter/material.dart';
class ApiWidget extends StatefulWidget {
#override
_ApiWidgetState createState() => _ApiWidgetState();
}
class _ApiWidgetState extends State<ApiWidget> {
Repository _repository = Repository();
Future<AccessToken> accessTokenFuture;
bool isButtonPressed = false;
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
FloatingActionButton(onPressed: () {
setState(() {
try {
isButtonPressed = true;
accessTokenFuture = fetchAccessToken();
} catch (_) {
print('Fetch error');
}
});
}, child: Icon(Icons.add),),
if(isButtonPressed)
FutureBuilder<AccessToken>(
future: bloc.fetchAccessToken(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Text('Error');
}
Column(
children: <Widget>[Text(snapshot.data)],
);
},
),
],);
}
}

You can do something like that:
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
FloatingActionButton(onPressed: () {
setState(() {
try {
isLoading = true;
accessTokenFuture = await fetchAccessToken();
isLoading = false;
} catch (_) {
isLoading = false;
print('Fetch error');
}
});
}, child: Icon(Icons.add),),
_buildAsyncInfo(),
],);
}
Widget _buildAsyncInfo() {
return isLoading ?
CircularProgressIndicator() :
Column(
children: <Widget>[Text(snapshot.data)],
);
}

Related

can't see circularprogressindicator while getting data from api in flutter

I am trying to show data from api and while loading data , there should be shown a circularprogressindicator,
but when I start app..it directly showing data instead of circularprogressindicator
class _HomeScreenState extends State<HomeScreen> {
bool isloading = false;
var maplist ;
Future<void> fetchdata() async {
setState(() {
isloading=true;
});
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
maplist = json.decode(resp.body);
setState(() {
isloading = false;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
fetchdata();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: MyBody(),
));
}
MyBody() {
return isloading==true ? Center(child: CircularProgressIndicator()) : ListView.builder(
itemCount: maplist.length,
itemBuilder: (context,index){
return Container(
padding: EdgeInsets.all(8.0),
child: Text(maplist[index]['title']));
});
}
}
It's actually working perfectly fine, it shows too fast because it is receiving data quickly(+ could be cache case).
If you like to have more delay you can add, future.delay which is unnecessary
Future<void> fetchdata() async {
setState(() {
isloading = true;
});
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
maplist = json.decode(resp.body);
// get more delay
await Future.delayed(Duration(seconds: 2));
setState(() {
isloading = false;
});
}
A better of way of handling future method with FutureBuilder
Try the following code:
class _HomeScreenState extends State<HomeScreen> {
var maplist;
Future<void> fetchdata() async {
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
setState(() {
maplist = json.decode(resp.body);
}
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: MyBody(),
));
}
MyBody() {
return FutureBuilder(
future: fetchdata(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
return ListView.builder(
itemCount: maplist.length,
itemBuilder: (context,index){
return Container(
padding: EdgeInsets.all(8.0),
child: Text(maplist[index]['title']));
});
}
}
}
You need to use FutureBuilder, it is not good to use async function in initState, try this:
FutureBuilder<List<Map<String,dynamic>>>(
future: fetchdata(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
List<Map<String,dynamic>> data = snapshot.data ?? [];
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.all(8.0),
child: Text(data[index]['title']));
});
}
}
},
),
also you need to change your fetchdata to this:
Future<List<Map<String,dynamic>>> fetchdata() async {
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
return json.decode(resp.body);
}
Try this one,set isloading default true
class _HomeScreenState extends State<HomeScreen> {
bool isloading = true;
var maplist ;
Future<void> fetchdata() async {
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
maplist = json.decode(resp.body);
setState(() {
isloading = false;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
fetchdata();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: MyBody(),
));
}
MyBody() {
return isloading ? Center(child: CircularProgressIndicator()) : ListView.builder(
itemCount: maplist.length,
itemBuilder: (context,index){
return Container(
padding: EdgeInsets.all(8.0),
child: Text(maplist[index]['title']));
});
}
}
You can use like that
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool isloading = false;
var maplist;
Future<void> fetchdata() async {
setState(() {
isloading = true;
});
var resp = await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
maplist = json.decode(resp.body);
setState(() {
isloading = false;
});
}
#override
void initState() {
super.initState();
fetchdata();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: isloading ? const CircularProgressIndicator() : const MyBody(),
);
}
}
class MyBody extends StatelessWidget {
const MyBody({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
//Write your code here
);
}
}

Why is flutter printing out widget name?

I have a problem with flutter printing out the name and rendering Widget name after running the application
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool? loggedIn = prefs.getBool('loggedin');
if (loggedIn == true) {
Home();
} else {
return LoginOrSignup();
}
}
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(body:SafeArea(
child: FutureBuilder(
future: autoLogin(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return Text('${snapshot.data}');
} else {
return LoginOrSignup();
}
}),
))
);
}
}
After running the app the output is LoginOrSignup()
class LoginOrSignup extends StatelessWidget {
const LoginOrSignup({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: MaterialButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Login()),
);
},
child: Text('Loginsss'),
),
),
Center(
child: MaterialButton(
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Signup()),
);
},
child: Text('Signup'),
),
)
],
),
);
}
}
I have tried using another widget like Text() but it still prints out the same when i run the application on a mobile app. The problem seems to appear in the autoLogin() function that i have
The issue is your future return Widget itself, and when you use Text('${snapshot.data}') it print the widget, To simplfity this you can return data from Future(this is what mostly we do). Let say you like to return widget itself.
A little correction is needed on Future.
Future<Widget> autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool? loggedIn = prefs.getBool('loggedin');
if (loggedIn == true) {
return Home();
} else {
return LoginOrSignup();
}
}
And
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: FutureBuilder<Widget>(
future: autoLogin(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return snapshot.data!;
} else {
return LoginOrSignup();
}
}),
)));
You are returning a Widget in autoLogin function. Instead you should return a bool.
Future<bool?> autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool? loggedIn = prefs.getBool('loggedin');
if (loggedIn == null) return null;
if (loggedIn == true) {
return true;
} else {
return false;
}
}
Then in the FutueBuilder you can check if it's then return Home()
if (snapshot.hasData && snapshot.data! == true) {
return Home();
} else {
return LoginOrSignup();

Run bloc only when list is empty

Why my function in initState - the loop doesnt work?
I mean that I just wanna run function
_newsBloc.add(GetCompanyList());
Only in first time app run when list of users is empty.
I just wanna make it because when I'm changing activities to other I have to wait again to fetch data from my api, but i dont want it.
If it ask Api 1 time I do not need it anymore.
Then I'm checkig length of list in init state it always return 0.
Any ideas?
This is my code:
class _CompanyActivityState extends State<CompanyActivity> with SingleTickerProviderStateMixin {
List<Company> _users = [];
final CompanyBloc _newsBloc = CompanyBloc();
#override
void initState() {
super.initState();
if(_users.length < 0 ) {
_newsBloc.add(GetCompanyList());
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: NavigationDrawerWidget(),
appBar: buildAppBar(),
floatingActionButton: Stack(
children: <Widget>[
Stack(
body: createListViewWidgets());
}
Widget createListViewWidgets() {
return Container(
margin: EdgeInsets.all(8.0),
child: BlocProvider(
create: (_) => _newsBloc,
child: BlocListener<CompanyBloc, CompanyState>(
listener: (context, state) {
if (state is CompanyError) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(state.message),
));
}
},
child: BlocBuilder<CompanyBloc, CompanyState>(
builder: (context, state) {
if (state is CompanyInitial) {
return _buildLoading();
} else if (state is CompanyLoading) {
return _buildLoading();
} else if (state is CompanyLoaded) {
_users = state.company;
return _listViewCard(context, state.company);
} else if (state is CompanyError) {
return Container();
} else {
return Container();
}
},
),
),
),
);
}
}
Bloc which works;
class CompanyBloc extends Bloc<CompanyEvent, CompanyState> {
List<Company> mList = [];
final ApiRepository _apiRepository = ApiRepository();
CompanyBloc() : super(CompanyInitial()) {
on<GetCompanyList>((event, emit) async {
try {
emit(CompanyLoading());
mList.addAll(await _apiRepository.fetchCompanyList());
emit(CompanyLoaded(mList));
if (mList.length == 0) {
emit(CompanyError('List is empty'));
}
} on NetworkError {
emit(CompanyError("Failed to fetch data. is your device online?"));
}
});
}
}
And state which is used to create _users list:
class CompanyLoaded extends CompanyState {
final List<Company> company;
const CompanyLoaded(this.company);
#override
List<Object> get props => throw UnimplementedError();
}

SearchDelagate displaying duplicate results

i am using search delegate to implement search function. Using API to fetch data, everything seems to be working fine, only problem is that every time i type a letter, list seems to rebuild and shows duplicate result.
Below is the code for search delegate class.
All your help will be very useful.
Thanks in advance.
class SearchBar extends SearchDelegate {
List<LeadModel> names = [];
List recentSearch = [];
getNames() async {
http
.get(
Uri.https('jsonplaceholder.typicode.com', 'users'),
)
.then((data) {
return json.decode(data.body);
}).then((data) {
for (var json in data) {
names.add(LeadModel.fromJson(json));
}
print(names.length);
}).catchError((e) {
print(e);
});
}
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
onPressed: () {
query = '';
},
icon: Icon(Icons.clear))
];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
onPressed: () {
close(context, '');
},
icon: Icon(Icons.arrow_back));
}
#override
Widget buildResults(BuildContext context) {
return Container();
}
#override
Widget buildSuggestions(BuildContext context) {
final suggestions = names.where((name) {
return name.name.toLowerCase().contains(query.toLowerCase());
});
return FutureBuilder(
future: getNames(),
builder: (context, snapshot) {
if (query.isEmpty) return Center(child: Text(""));
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: Text(""));
default:
if (snapshot.hasError) {
return Center(child: Text(""));
} else {
names.clear();
return ListView.builder(
itemCount: suggestions.length,
itemBuilder: (context, i) {
return ListTile(
title: Text(suggestions.elementAt(i).name),
);
},
);
}
}
},
);
}
}
In your getNames functions, clear the list names before adding items to it, like this:
getNames() async {
names.clear();
await http
.get(
Uri.https('jsonplaceholder.typicode.com', 'users'),
)
fixed it with the help from #Huthaifa Muayyad, working code is
class SearchBar extends SearchDelegate {
List<LeadModel> names = [];
List recentSearch = [];
getNames() async {
names.clear();
await http
.get(
Uri.https('jsonplaceholder.typicode.com', 'users'),
)
.then((data) {
return json.decode(data.body);
}).then((data) {
for (var json in data) {
names.add(LeadModel.fromJson(json));
}
print(names.length);
}).catchError((e) {
print(e);
});
}
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
onPressed: () {
query = '';
},
icon: Icon(Icons.clear))
];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
onPressed: () {
close(context, '');
},
icon: Icon(Icons.arrow_back));
}
#override
Widget buildResults(BuildContext context) {
return Container();
}
#override
Widget buildSuggestions(BuildContext context) {
final suggestions = names.where((name) {
return name.name.toLowerCase().contains(query.toLowerCase());
});
return FutureBuilder(
future: getNames(),
builder: (context, snapshot) {
if (query.isEmpty) return Center(child: Text(""));
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: Text(""));
default:
if (snapshot.hasError) {
return Center(child: Text(""));
} else {
return ListView.builder(
itemCount: suggestions.length,
itemBuilder: (context, i) {
return ListTile(
title: Text(suggestions.elementAt(i).name),
);
},
);
}
}
},
);
}
}

navigation transition hangs during Future

i have the below code , that when the button is pressed on Page1() , the future is executed when Page2() loads, but the CircularProgressIndicator() "freezes", until the future completes. I have tried this with BottomNavigationBar as well, and the "slide-in" freezes half way there as well.
is there a more idiomatic way to do this so that Page2() renders fully while the future is running ?
//-----------------------
//main.dart
//-----------------------
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
routes: {
'/' : (BuildContext context) => Page1(),
'/page2' : (BuildContext context) => Page2()
}
);
}
}
//-----------------------
//page1.dart
//-----------------------
class Page1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Page 1')),
body: Container(
child: Column(children: <Widget>[
Text('Page 1 header'),
RaisedButton(
child: Text('Click me'),
onPressed: () {
Navigator.of(context).pushReplacementNamed('/page2');
})
],),)
);
}
}
//-----------------------
//page2.dart
//-----------------------
class Page2 extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _Page2State();
}
}
class _Page2State extends State<Page2> {
MainModel model = MainModel();
void initState() {
model.fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Page 2')),
body: ScopedModel<MainModel>(
model: model,
child: ScopedModelDescendant(
builder: (context, child, MainModel model) {
if (model.isLoading) {
return Center(child: CircularProgressIndicator());
} else {
return Container(
child: Column(
children: <Widget>[
Text('Page 2 body'),
],
));
}
})));
}
}
//-----------------------
//main_model.dart
//-----------------------
class MainModel extends Model {
bool _isLoading = false;
bool get isLoading => _isLoading;
void fetchData() async {
_isLoading = true;
notifyListeners();
final String url = 'https://jsonplaceholder.typicode.com/todos/1';
await http.get(url)
.then<Null>((http.Response response) {
print('${DateTime.now()} In http response and about to sleep');
sleep(const Duration(seconds:5));
print('${DateTime.now()} done sleeping');
_isLoading = false;
notifyListeners();
return;
}
).catchError((error) {
print('Error: $error');
_isLoading = false;
notifyListeners();
return;
});
}
}
The problem is that the sleep() method freezes the ui.
You could try a Future.delayed() in the following way:
class MainModel extends Model {
bool _isLoading = false;
bool get isLoading => _isLoading;
void fetchData() {
_isLoading = true;
notifyListeners();
final String url = 'https://jsonplaceholder.typicode.com/todos/1';
http.get(url).then<Null>((http.Response response) {
print('${DateTime.now()} In http response and about to sleep');
Future.delayed(Duration(seconds: 5), () {
_isLoading = false;
notifyListeners();
print('${DateTime.now()} done sleeping');
});
}).catchError((error) {
print('Error: $error');
_isLoading = false;
notifyListeners();
return;
});
}
}
Flutter recommends using the FutureBuilder widget when working with async data sources. For example:
FutureBuilder<Post>(
future: fetchPost(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.title);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner
return CircularProgressIndicator();
},
);
This way the CircularProgressIndicator will keep running whilst your Page2 loads the API data.