Run bloc only when list is empty - flutter

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

Related

Scan for bluetooth devices Flutter

I am new to Bluetooth and this is the first time I am trying it, so I am trying to develop an application that scans for nearby Bluetooth devices and connects,
I am using flutter_blue package and I wrote this simple code to scan for devices, and there are two devices near it but when I run the app (real phone) it does not discover them, I want to know what is wrong with my code, I need help please.
class _MyHomePageState extends State<MyHomePage> {
final FlutterBlue flutterBlue= FlutterBlue.instance;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("BLE"),),
body: StreamBuilder<BluetoothState>(
stream:flutterBlue.state,
builder: (context, snapshot){
if(snapshot.data == BluetoothState.on)
{
return ScanForDevices(flutterBlue: flutterBlue,);
//return Center(child: Text("${snapshot.data!.toString()}"),);
}
else if(snapshot.hasError ||snapshot.data == BluetoothState.unauthorized)
{
return Center(child: Text("Error"),);
}
else{
return Center(child: CircularProgressIndicator(),);
}
},
),
);
}
}
class _ScanForDevicesState extends State<ScanForDevices> {
late List<ScanResult> result ;
#override
void initState() {
// TODO: implement initState
super.initState();
widget.flutterBlue.startScan();
// scanning();
}
scanning() {
widget.flutterBlue.scanResults.listen((event) {
for(ScanResult r in event)
{
result.add(r);
print(r.device.id.toString() + "\n");
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<List<ScanResult>>(
stream:FlutterBlue.instance.scanResults,
builder: (context, snapshot){
if(snapshot.hasData)
{
return ListView(
// children: snapshot.data!.map((e) {
// return Text("${e.device.id.toString()}");
// }).toList(),
children: snapshot.data!.map((e) => ListTile(
leading: IconButton(
icon: Icon(Icons.present_to_all_sharp),
onPressed: (){e.device.connect(timeout: Duration(seconds: 4),);},),
subtitle: Text("${e.device.name }\n ${e.device.id.id}"),
title: Text(e.device.id.toString()))).toList()
// result.map((e) {
// return Text(e.device.id.toString());
// }).toList(),
);
}
if(snapshot.hasError)
{
return Text("Error");
}
else {
return CircularProgressIndicator();
}
},
),
);
}
}
and it shows these log messages

How to pass Future<Either<Response, List<dynamic>>> function as parameter to Widget?

I created a general ListPage as below:
class ListPage extends StatelessWidget {
final context;
final Future<Either<Response, List<dynamic>>> futureFuncion;
final classListItem, classDetailPage;
const ListPage(this.context,this.futureFuncion,this.classListItem,this.classDetailPage, {Key key,}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(10.0),
child: Column(children: [Expanded(child: _getData())]));
}
FutureBuilder _getData() {
return FutureBuilder<Either<ResponseError, List>>(
future: futureFuncion,
builder: (BuildContext context, AsyncSnapshot<Either<ResponseError, List>> snapshot) {
if (snapshot.hasError) {
return globals.showSnapshotError(snapshot);
} else if (!snapshot.hasData) {
return globals.showLoading('Loading...');
} else {
return snapshot.data.fold((l) {
return Error().showError(l.error, l.errorDescription);
}, (r) {
return ListView(children: _buildList(context, r));
});
}
},
);
}
}
I use code as below to create List Page:
class RequestListPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
String url = globals.debug ? 'assets/data/list.json' : 'domain.com/list';
return Scaffold(
appBar: AppBar(toolbarHeight: 50,centerTitle: true,title: Text("List")),
body: ListPage(
context,
debug
? LoadDataFromJsonFile().loadListData(context, url, '$Response')
: RequestApi().getListData(url, '$Response'),
RequestListItem,
RequestDetailPage,
),
);
}
Widget _buildList(context, list) {
List<Widget> children = [];
list.forEach((item) {
children.add(_buildTile(context, item));
});
return ListView(children: children);
}
ListTile _buildTile(context, item) {
return ListTile(
title: _buildListItem(context, item),
onTap: () => showDetail(context, item),
);
}
Widget _buildListItem(context, item) {
return classListItem;
}
void showDetail(context, item) {
Navigator.of(context).push(
MaterialPageRoute(builder: (BuildContext context) => classDetailPage(context, item)),
);
}
}
}
When I run this code, I got error as below, what can I do?
Attempted to use type 'RequestListItem' as a function. Since types do not define a method 'call', this is not possible. Did you intend to call the RequestListItem constructor and forget the 'new' operator?
Receiver: RequestListItem
Tried calling: RequestListItem(Instance of 'StatefulElement', Instance of 'Response')

Checking bloc state with blocbuilder

I'm fetching a list of items from an API and building them using the BlocBuilder. It works and the widget list is built but when I print out to check which part of the state is being executed, I get as shown below. Why does 'Nothing' appeared ?
ProductInitial
fetching product //from bloc when fetching api
Nothing
fetching complete //from bloc after fetching api
ProductSuccess
Main
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<ProductBloc>(
create: (BuildContext context) => ProductBloc()..add(FetchProduct())
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: MainScreen(),
)
);
}
}
List Screen
Widget build(BuildContext context) {
return Container(
child: Padding(
padding: EdgeInsets.all(10.0),
child: BlocBuilder<ProductBloc, ProductState>(
builder: (context, state) {
if(state is ProductInitial){
print('ProductInitial');
return buildLoadingWidget();
}
if(state is ProductSuccess){
print('ProductSuccess');
return _buildProductListWidget(state.products);
}
if(state is ProductFailed){
print('ProductFailed');
return Center(
child: Text('Something went wrong'),
);
}
print('Nothing');
return buildLoadingWidget();
}
)
)
);
}
Update
Added the bloc code for reference.
Bloc
class ProductBloc extends Bloc<ProductEvent, ProductState> {
ProductBloc() : super(ProductInitial());
ProductRepository _repository = ProductRepository();
#override
Stream<ProductState> mapEventToState(ProductEvent event,) async* {
if(event is FetchProduct){
yield ProductLoading();
try{
print('fetching product');
final List<ProductModel> products = await _repository.getProducts();
yield ProductSuccess(products);
print('fetching complete');
}catch(e){
yield ProductFailed();
print(e);
print('fetching failed');
}
}
}
}
Update your bloc with the below code:
BLOC
class ProductBloc extends Bloc<ProductEvent, ProductState> {
ProductBloc() : super(ProductInitial());
ProductRepository _repository = ProductRepository();
#override
Stream<ProductState> mapEventToState(ProductEvent event,) async* {
if(event is FetchProduct){
yield ProductInitial();
try{
print('fetching product');
final List<ProductModel> products = await _repository.getProducts();
yield ProductSuccess(products);
print('fetching complete');
}catch(e){
yield ProductFailed();
print(e);
print('fetching failed');
}
}
}
}
As in your code, you are yield ProductLoading state but didn't handle that state in your BlocBuilder that's why it bypasses all if statement and prints Nothing.
So another way is handle ProductLoading in your block build as shown below
List Screen
Widget build(BuildContext context) {
return Container(
child: Padding(
padding: EdgeInsets.all(10.0),
child: BlocBuilder<ProductBloc, ProductState>(
builder: (context, state) {
if(state is ProductInitial){
print('ProductInitial');
return buildLoadingWidget();
}
if(state is ProductLoading){
print('ProductLoading');
return buildLoadingWidget();
}
if(state is ProductSuccess){
print('ProductSuccess');
return _buildProductListWidget(state.products);
}
if(state is ProductFailed){
print('ProductFailed');
return Center(
child: Text('Something went wrong'),
);
}
print('Nothing');
return buildLoadingWidget();
}
)
)
);
}

Button pressed return a future builder

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

How to use flutter provider in a statefulWidget?

I am using flutter_provider for state management. I want to load some items on page(statefulwidget) load from Api. I am showing a loader on page start and want to show the items once they are fetched.
PlayList.dart -
class Playlist extends StatefulWidget {
#override
_PlaylistState createState() => _PlaylistState();
}
class _PlaylistState extends State<Playlist> {
var videosState;
#override
void initState() {
super.initState();
videosState = Provider.of<VideosProvider>(context);
videosState.fetchVideosList();
}
#override
Widget build(BuildContext context) {
var videos = videosState.playlist;
return Scaffold(
appBar: AppBar(
title: Text('My Videos'),
),
body: RefreshIndicator(
child: Container(
width: double.infinity,
height: double.infinity,
child: videos.length
? ListView.builder(
itemBuilder: (BuildContext context, index) {
return _videoListItem(context, index, videos, videosState);
},
itemCount: videos.length,
)
: Center(
child: CircularProgressIndicator(),
),
),
onRefresh: () => null,
),
);
}
}
My provider is like this -
class VideosProvider with ChangeNotifier {
List _playlist;
int _currentVideoId;
get playlist => _playlist;
void setPlayList(videosList) {
_playlist = videosList;
}
Future fetchVideosList() async {
http.Response response =
await http.get("http://192.168.1.22:3000/videos-list/");
print(json.decode(response.body));
videos = json.decode(response.body)["data"];
setPlayList(videos);
return videos;
}
}
This gives an error of -
inheritFromWidgetOfExactType(_Provider<VideosProvider>) or inheritFromElement() was called before _PlaylistState.initState() completed.
here is the build method of the parent of playList class, wrapped in a changenotifier,
Widget build(BuildContext context) {
return ChangeNotifierProvider<VideosProvider>(
builder: (BuildContext context) => VideosProvider(),
child: MaterialApp(
title: "My App",
home: new Playlist(),
),
);
}
So, all the examples on flutter_provider on internet show usage of provider on a statelesswidget, where state changes occur on user interactions like a button click. None about how to use provider in a statefulWidget, and cases where data has to be updated on page load without any interaction.
I am aware of streambuilder and futurebuilder for this kind of scenarios, but want to understand how this can be done with flutter_provider. How can I use provider to call fetchVideosList in initState(on pageload)? Does this case can/should be handled with a statelessWidget?
Does this case can/should be handled with a statelessWidget?
The answer is : No, it does not
I am heavy user of StatefulWidget + Provider. I always use this pattern for displaying a Form which contains fields, that available for future edit or input.
Updated : February 9 2020
Regarding to Maks comment, I shared better way to manage provider using didChangeDependencies.
You may check to this github repository
main.dart
First Step
Initiate PlayListScreen inside ChangeNotifierProvider
class PlaylistScreenProvider extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<VideosProvider>(
create: (_) {
return VideosProvider();
},
child: PlaylistScreen(),
);
}
}
class MainScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Screen'),
),
body: Center(
child: RaisedButton(
child: Text("Go To StatefulWidget Screen"),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) {
return PlaylistScreenProvider();
},
),
);
},
),
),
);
}
}
Second Step
Make PlaylistScreen as Stateful Widget to hold TextEditingContoller
and other values.
playlistScreen.dart
class PlaylistScreen extends StatefulWidget {
#override
_PlaylistScreenState createState() => _PlaylistScreenState();
}
class _PlaylistScreenState extends State<PlaylistScreen> {
List _playlistList;
String _errorMessage;
Stage _stage;
final _searchTextCtrl = TextEditingController();
#override
void dispose() {
super.dispose();
_searchTextCtrl.dispose();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
final videosState = Provider.of<VideosProvider>(context);
_playlistList = videosState.playlist;
_stage = videosState.stage;
_errorMessage = videosState.errorMessage;
}
void actionSearch() {
String text = _searchTextCtrl.value.text;
Provider.of<VideosProvider>(context, listen: false)
.updateCurrentVideoId(text);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My Videos'),
),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: <Widget>[
Container(
child: RaisedButton.icon(
icon: Icon(Icons.search),
label: Text("Filter"),
onPressed: () {
actionSearch();
},
),
),
Container(
child: TextField(
controller: _searchTextCtrl,
onSubmitted: (value) {
actionSearch();
},
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Please input 1 or 2',
),
),
),
Flexible(
child: _stage == Stage.DONE
? PlaylistTree(_playlistList)
: _stage == Stage.ERROR
? Center(child: Text("$_errorMessage"))
: Center(
child: CircularProgressIndicator(),
),
)
],
),
),
);
}
}
class PlaylistTree extends StatelessWidget {
PlaylistTree(this.playlistList);
final List playlistList;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: playlistList.length,
itemBuilder: (context, index) {
var data = playlistList[index];
return Container(
child: Text("${data['id']} - ${data['first_name']}"),
);
},
);
}
}
Last Step
make provider to handle Business Logic
videosProvider.dart
enum Stage { ERROR, LOADING, DONE }
class VideosProvider with ChangeNotifier {
String errorMessage = "Network Error";
Stage stage;
List _playlist;
int _currentVideoId;
VideosProvider() {
this.stage = Stage.LOADING;
initScreen();
}
void initScreen() async {
try {
await fetchVideosList();
stage = Stage.DONE;
} catch (e) {
stage = Stage.ERROR;
}
notifyListeners();
}
List get playlist => _playlist;
void setPlayList(videosList) {
_playlist = videosList;
}
void validateInput(String valueText) {
if (valueText == ""){
this._currentVideoId = null;
return;
}
try {
int valueInt = int.parse(valueText);
if (valueInt == 1 || valueInt == 2){
this._currentVideoId = valueInt;
}
else {
this.errorMessage = "Use only 1 and 2";
throw 1;
}
} on FormatException catch (e) {
this.errorMessage = "Must be a number";
throw 1;
}
}
void updateCurrentVideoId(String value) async {
this.stage = Stage.LOADING;
notifyListeners();
try {
validateInput(value);
await fetchVideosList();
stage = Stage.DONE;
} on SocketException catch (e) {
this.errorMessage = "Network Error";
stage = Stage.ERROR;
} catch (e) {
stage = Stage.ERROR;
}
notifyListeners();
}
Future fetchVideosList() async {
String url;
if (_currentVideoId != null) {
url = "https://reqres.in/api/users?page=$_currentVideoId";
} else {
url = "https://reqres.in/api/users";
}
http.Response response = await http.get(url);
var videosList = json.decode(response.body)["data"];
setPlayList(videosList);
}
}
Old answer : Aug 19 2019
In my case :
form_screen.dart
class Form extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<FormProvider>(
builder: (_) {
return FormProvider(id: ...); // Passing Provider to child widget
},
child: FormWidget(), // So Provider.of<FormProvider>(context) can be read here
);
}
}
class FormWidget extends StatefulWidget {
#override
_FormWidgetState createState() => _FormWidgetState();
}
class _FormWidgetState extends State<FormWidget> {
final _formKey = GlobalKey<FormState>();
// No need to override initState like your code
#override
Widget build(BuildContext context) {
var formState = Provider.of<FormProvider>(context) // access any provided data
return Form(
key: _formKey,
child: ....
);
}
}
FormProvider as a class, need to update their latest value from API. So, initially, it will request to some URL and updates corresponding values.
form_provider.dart
class FormProvider with ChangeNotifier {
DocumentModel document;
int id;
FormProvider({#required int id}) {
this.id = id;
initFormFields(); // will perform network request
}
void initFormFields() async {
Map results = initializeDataFromApi(id: id);
try {
document = DocumentModel.fromJson(results['data']);
} catch (e) {
// Handle Exceptions
}
notifyListeners(); // triggers FormWidget to re-execute build method for second time
}
In your case :
PlayList.dart
class PlaylistScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<VideosProvider>(
builder: (_) {
return VideosProvider(); // execute construct method and fetchVideosList asynchronously
},
child: Playlist(),
);
}
}
class Playlist extends StatefulWidget {
#override
_PlaylistState createState() => _PlaylistState();
}
class _PlaylistState extends State<Playlist> {
final _formKey = GlobalKey<FormState>();
#override
void initState() {
super.initState();
// We *moved* this to build method
// videosState = Provider.of<VideosProvider>(context);
// We *moved* this to constructor method in provider
// videosState.fetchVideosList();
}
#override
Widget build(BuildContext context) {
// Moved from initState
var videosState = Provider.of<VideosProvider>(context);
return Scaffold(
appBar: AppBar(
title: Text('My Videos'),
),
body: RefreshIndicator(
}
}
provider.dart
class VideosProvider with ChangeNotifier {
VideosProvider() {
// *moved* from Playlist.initState()
fetchVideosList(); // will perform network request
}
List _playlist;
int _currentVideoId;
get playlist => _playlist;
void setPlayList(videosList) {
_playlist = videosList;
}
Future fetchVideosList() async {
http.Response response =
await http.get("http://192.168.1.22:3000/videos-list/");
print(json.decode(response.body));
videos = json.decode(response.body)["data"];
setPlayList(videos);
// return videos; // no need to return
// We need to notify Playlist widget to rebuild itself for second time
notifyListeners(); // mandatory
}
}
When using Provider for state management you don't need to use StatefullWidget, so how can you call a method of the ChangeNotifier on start of the app?
You can simply do that in the constructor of the ChangeNotifier, so that when you point out VideosProvider() to the ChangeNotifierProvider Builder the constructor will get called the first time the provider constructs the VideosProvider, so:
PlayList.dart:
class Playlist extends StatelessWidget {
#override
Widget build(BuildContext context) {
final videosState = Provider.of<VideosProvider>(context);
var videos = videosState.playlist;
return Scaffold(
appBar: AppBar(
title: Text('My Videos'),
),
body: RefreshIndicator(
child: Container(
width: double.infinity,
height: double.infinity,
child: videos.length
? ListView.builder(
itemBuilder: (BuildContext context, index) {
return _videoListItem(context, index, videos, videosState);
},
itemCount: videos.length,
)
: Center(
child: CircularProgressIndicator(),
),
),
onRefresh: () => null,
),
);
}
}
VideosProvider.dart:
class VideosProvider with ChangeNotifier {
VideosProvider(){
fetchVideosList();
}
List _playlist;
int _currentVideoId;
get playlist => _playlist;
void setPlayList(videosList) {
_playlist = videosList;
}
Future fetchVideosList() async {
http.Response response =
await http.get("http://192.168.1.22:3000/videos-list/");
print(json.decode(response.body));
videos = json.decode(response.body)["data"];
setPlayList(videos);
return videos;
}
}
When using a Provider you don’t need to use a StatefulWidget (as of a tutorial by the Flutter team State management
You may use the following tutorial to see how to fetch data with a provider and a
StatelessWidget: Flutter StateManagement with Provider