Should I create a bloc builder in each view flutter - flutter

I am creating an application for managing shopping list, products and users using firestore and flutter, and I am starting to learn the bloc pattern. I have 2 blocs in my app the AuthBloc for users, and the ShoppingBloc for shopping lists. Right now I display the list of shopping lists of an user, and when I press a button I want to go to another screen to create a new shopping list. I want that when I press the button I change the state and when the state is change a listener (or something similar) changes the view.
My ShoppingListView is:
#override
Widget build(BuildContext context) {
context.read<ShoppingBloc>().add(const ShoppingEventInitialize());
return BlocConsumer<ShoppingBloc, ShoppingState>(
listener: (context, state) {
if (state is ShoppingCartState) {
print('El estado es shoppingCartState');
} else {
print('El estado es ' + state.toString());
}
},
builder: (context, state) {
if (state is ShoppingCartState) {
return Scaffold(
appBar: AppBar(
title: const Text('Your Shopping Cart Lists'),
actions: [
IconButton(
onPressed: () {
context.read<ShoppingBloc>().add(CreateEvent());
},
icon: const Icon(Icons.add),
),
PopupMenuButton<MenuAction>(
onSelected: (value) async {
switch (value) {
case MenuAction.logout:
final shouldLogout = await showLogOutDialog(context);
if (shouldLogout) {
context.read<AuthBloc>().add(const AuthEventLogout());
}
}
},
itemBuilder: (context) {
return [
const PopupMenuItem<MenuAction>(
value: MenuAction.logout,
child: Text('Logout'),
),
];
},
)
],
),
body: StreamBuilder(
stream:
_shoppingCartService.getShoppingCartLists(ownerUserId: userId),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
case ConnectionState.active:
if (snapshot.hasData) {
final allShoppingCartLists =
snapshot.data as Iterable<CloudShoppingCartList>;
return ShoppingCartListView(
shoppingCartLists: allShoppingCartLists,
onDeleteShoppingCartList: (shoppingCartList) async {
await _shoppingCartService.deleteShoppingCart(
shoppingCartListId:
shoppingCartList.shoppingCartListId);
},
onTap: (shoppingCartList) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ShoppingCartListDetailsView(
shoppingCartId:
shoppingCartList.shoppingCartListId,
shoppingCartName: shoppingCartList
.name)),
);
},
);
} else {
return const CircularProgressIndicator();
}
default:
return const CircularProgressIndicator();
}
})
);
} else {
return (const CreateUpdateShoppingCartListView());
}
And the CreateShoppingListView is:
#override
Widget build(BuildContext context) {
return BlocBuilder<ShoppingBloc, ShoppingState>(
builder: (context, state) {
if (state is ShoppingCartState) {
return ShoppingCartView();
} else
return Scaffold(
appBar: AppBar(
title: const Text('New shopping cart list'),
actions: [
IconButton(
onPressed: () {
final text = _textController.text;
final ownerUserId = currentUser.id;
context.read<ShoppingBloc>()
.add(ShoppingCreateNewShoppingCartEvent(
ownerUserId, text));
},
icon: const Icon(Icons.add),
),
IconButton(
onPressed: () async {
final text = _textController.text;
if (_shoppingCartList == null || text.isEmpty) {
await showCannotShareEmptyNoteDialog(context);
} else {
Share.share(text);
}
},
icon: const Icon(Icons.share),
)
],
),
body: Column(children: [
TextField(
controller: _textController,
keyboardType: TextInputType.multiline,
maxLines: null,
textInputAction: TextInputAction.go,
decoration: const InputDecoration(
hintText: 'Start typing you shopping cart name...',
),
),
],
}
}
I needed to create a bloc builder in the CreateShoppingListView to listen to the shoppingState and change view if the state is ShoppingCartState. My question is, it is necessary to create a bloc builder in each view to react to the states changes or is there a way to create a bloc builder that works for all views. I don't know if I explained myself corretly.
Thank you in advance

Related

LateInitializationError error in flutter_map

I set up flutter_map succesfully, but when I try to filter my map by "City" for example I am getting this error:
The following LateError was thrown building FutureBuilder<List<dynamic>>(dependencies: [MediaQuery],
state: _FutureBuilderState<List<dynamic>>#cb20d):
LateInitializationError: Field '_state' has already been initialized.
The relevant error-causing widget was:
FutureBuilder<List<dynamic>>
My flutter_map implementation is as follow:
late MapController mapController;
Future<List<dynamic>>? futureLocs;
Future<List<dynamic>>? futureLocsFilteredByCity;
bool? isFilterByCity;
PageController pageController = PageController();
double currentZoom = 10.0;
PanelController panelController = PanelController();
#override
void initState() {
super.initState();
mapController = MapController();
pageController = PageController(viewportFraction: 0.7, initialPage: 0);
futureLocs = getAllDogsLocation();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: GenericAppBar(context,
backbutton: true,
title: 'Dogs map',
filterbutton: true, onfilterpress: () {
showDialog(
context: context,
builder: (context) {
return CitiesToFilter(
futureLocs: futureLocs,
onCityPress: (city) {
setState(() {
isFilterByCity = true;
futureLocs = getDogLocationByCity(city);
futureLocs!.then((value) {
if (value.isNotEmpty) {
var latlong = LatLng(
value[0]['latitude'], value[0]['longitude']);
widget.lat = latlong.latitude;
widget.long = latlong.longitude;
}
});
});
});
});
}),
body: FlutterMapCusto(
futureLocs: futureLocs,
mapController: mapController,
pageController: pageController,
lat: widget.lat,
long: widget.long,
panelcontroller: panelController,
),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: Text('CurrentLoc'),
onPressed: () {
setState(() {
mapController.move(
LatLng(widget.lat, widget.long), currentZoom);
});
},
tooltip: 'Current location',
child: const Icon(Icons.location_history),
),
],
));
}
}
where FlutterMapCusto widget is defined as a normal widget with FlutterMap class. I am not including it to avoid boilerplate code here since it is a basic implementation found in the package web. I think the error is coming from mapController..
On the other hand I am fetching my new data filtered by city with the function "getDogLocationByCity(city)" updating my future.
Then we have CitiesToFilter widget:
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Filter'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Filter by City'),
FloatingActionButton(onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Cities:'),
content: SizedBox(
width: MediaQuery.of(context).size.width,
child: FutureBuilder(
future: widget.futureLocs,
builder: (BuildContext context,
AsyncSnapshot<List<dynamic>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Text('Loading...');
case ConnectionState.active:
{
return const Center(
child: Text('Loading...'),
);
}
case ConnectionState.done:
if (snapshot.hasError) {
return Text(
'Error: ${snapshot.error}');
}
if (snapshot.hasData) {
return ListView.builder(
itemCount:
snapshot.data!.length,
itemBuilder: (context, index) {
return TextButton(
onPressed: () {
setState(() {
widget.onCityPress( snapshot.data![index]['CityName'] );
});
Navigator.pop(context);
},
child: Text(
snapshot.data![index]
['CityName']));
});
} else {
return const Text(
'No data available');
}
}
},
),
),
);
});
})
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Close'))
],
);
}
Future method to fetch the data shown in map. This is just a wrapper developed from Back4App to interact with its MongoDb database:
Future<List<dynamic>> getAllDogsLocation() async {
await Future.delayed(const Duration(seconds: 2), () {});
QueryBuilder<ParseObject> queryTodo =
QueryBuilder<ParseObject>(ParseObject('Todo'));
// queryTodo.includeObject(['latitude']);
final ParseResponse apiResponse = await queryTodo.query();
if (apiResponse.success && apiResponse.results != null) {
return apiResponse.results as List<ParseObject>;
} else {
throw Exception('Failed to load data');
}
}

How to place in one widget tree several widgets flutter?

What is the right way to place all these widgets together? When I tried to place together widget which is building the listview. separated widget and the widgets where I am creating the UI for filters, it draws only widget with filtering items but doesn't create the listview. I was searching that I should place it in the column widget and in the expanded but it also doesn't work as I want.
Here is my code:
class _ProductListState extends State<ProductList> {
#override
Widget build(BuildContext context) {
var providerGridPagination = Provider.of<ProviderGridProduct>(context);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
title: Text("Категории товаров", style: TextStyle(color: Colors.black45),),
leading: IconButton(onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const Home()),
);
}, icon: Icon(Icons.arrow_back, color: Colors.black45,),),
),
body: Column(
children: [
FiltersWidget(),
SmartRefresher(
controller: providerGridPagination.refreshController,
enablePullUp: true,
onRefresh: () async {
final result = await providerGridPagination.getProductData(isRefresh: true);
if (result) {
providerGridPagination.refreshController.refreshCompleted();
} else {
providerGridPagination.refreshController.refreshFailed();
}
},
onLoading: () async {
final result = await providerGridPagination.getProductData();
if (result) {
providerGridPagination.refreshController.loadComplete();
} else {
providerGridPagination.refreshController.loadFailed();
}
},
child: ListView.separated(
itemBuilder: (context, index) {
//final gridItems = providerGridPagination.itemgrid[index];
return ListTile(
title: Text(providerGridPagination.itemgrid[index].title!),
);
},
separatorBuilder: (context, index) => Divider(),
itemCount: providerGridPagination.itemgrid.length,
),
),
],
),
);
}
}
the error is:
The following assertion was thrown during a scheduler callback:
This widget has been unmounted, so the State no longer has a context (and should be considered defunct).

Search Filter on ListView Flutter

I have a problem about filtering the data that I get from the json response. I already put the service initialization in initSate instead of future in FutureBuilder but it's still not working. Maybe I miss something in the filter function?
initState :
void initState() {
doctorService = DoctorService();
_doctorData = doctorService.getDoctors();
super.initState();
}
FutureBuilder:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Daftar Dokter"),),
body:
FutureBuilder<List<Doctor>>(
future: _doctorData,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.hasError) {
print(snapshot);
return Center(
child: Text("Error"),
);
}
else if (snapshot.hasData){
doctors = snapshot.data;
tempDoctorData = List.from(doctors);
return _buildListView(tempDoctorData);
}
else {
return Center(
child: Container(),
);
}
},
),
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.white,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (BuildContext buildContext)=>FormAlbum())
);
},
),
);
}
And this is the filter function
onItemChanged(String value) {
setState(() {
tempDoctorData = doctors.where((element) => element.name.toLowerCase().contains(value.toLowerCase())).toList();
});
}

FutureBuilder Completely Unresponsive

My process is as follows. The screen has just two elements - TextFormField and an ElevatedButton.
Get email address from user
User clicks button
Button validates input, then
Calls FutureBuilder, which
Tries to fetch client record from REST API
Redirects to appropriate route
This is my first Flutter/Dart program FYI, so I might be making a beginner mistake.
Question: The very first line of the FutureBuilder isn't executed. No error, no messages, nothing. Why does this happen?
The user enters the email address, clicks the button, the fetchClientInfo function is executed, which returns a Future<ClientInfo> and that's that.
Could you help please?
#override
Widget build(BuildContext context) {
final _formKey = GlobalKey<FormState>();
return Scaffold(
appBar: AppBar(
title: Text("Register Profile"),
),
body: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.all(20),
child: TextFormField(
controller: emailController,
validator: (email) {
if (email.isEmpty) {
return 'Please enter your email address.';
} else if (!EmailValidator.validate(email)) {
return 'Please enter a valid email address.';
}
return null;
},
decoration: InputDecoration(
border: new UnderlineInputBorder(borderSide: new BorderSide(color: Colors.red)),
labelText: 'Email',
hintText: 'Enter your email address',
contentPadding: EdgeInsets.all(20.0),
),
)),
ElevatedButton(
onPressed: () => {
if (_formKey.currentState.validate())
{
FutureBuilder<ClientInfo>(
future: fetchClientInfo(emailController.text),
builder: (BuildContext context, snapshot) {
print("here");
if (snapshot.data.outcome) {
return Text("main screen");
} else if (!snapshot.data.outcome) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
RegisterNewUser(emailAddress: emailController.text)));
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// Show a spinner
return CircularProgressIndicator();
},
)
}
},
child: Text(
"Check Email",
))
])));
}
Future<ClientInfo> fetchClientInfo(String emailAddress) async {
var url = Uri.https(APIAccess.baseAPIURL, APIAccess.pathToClientAPI, {
'client_id': '$emailAddress',
'action': 'info',
'key': '${APIAccess.key}'
});
final response = await http.get(url);
if (response.statusCode == 200) {
return ClientInfo.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load album');
}
}
You're missing a couple of things:
return statement (in your lambda you're creating a FutureBuilder but it's not being used anywhere)
if(true) {
return SizedBox.shrink();
}
correct lambda syntax (well, technically it's correct but it's not doing what you want): https://dart.dev/guides/language/language-tour#anonymous-functions
// that's how compiler sees it
Map<dynamic, dynamic> Function() foo = () => {
};
What you did reminds me of javascript, but in dart lambdas look a bit different
return Button(
onTap: () => doStuff(),
);
return Button(
onTap: () {
doStuff();
}
);
// and if you want to return a value from block lambda
return Builder(
builder: (context) {
return SizedBox.shrink();
}
);
rendering widget on tap
When handling tap events, it's best to redirect calls to a component that's handling business logic, and only listen for current state in the widget.
What you want to read about is state management. The topic is highly opinionated, so you have to choose yourself the solution that's right for you. https://flutter.dev/docs/development/data-and-backend/state-mgmt
I myself like using a slightly modified version of bloc. You can find the 'original' one here: https://pub.dev/packages/flutter_bloc
A new de-facto standard if it comes to state management is Riverpod
If you just want to make your code work, do something like this:
class Demo extends StatefulWidget {
#override
_DemoState createState() => _DemoState();
}
class _DemoState extends State<Demo> {
Future<ClientInfo?> clientInfo = Future.value(null);
#override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: () {
setState(() {
clientInfo = fetchClientInfo(emailController.text);
});
},
child: _buildButtonContent(),
),
FutureBuilder<ClientInfo>(
initialData: null,
future: clientInfo,
builder: (BuildContext context, snapshot) {
if (snapshot.data == null) {
return SizedBox.shrink();
} else {
return Text(snapshot.data.toString());
}
},
)
],
);
}
}

Flutter: Search Delegate black screen

I created a search option by using search delegate. Everything is fine in search delegate. But when I pressed on search button in keyboard, it's show me a black screen. Why it's react like this ? And how can I solve it ?
here is my homepage.dart code for search icon -
FutureBuilder(
future: fetchBooks(),
builder: (context, snapshot) {
return IconButton(
icon: Icon(
Icons.search,
color: _whiteCream,
),
onPressed: () async {
var booksSearchData = snapshot.data
.where((b) =>
b.category == 1 ||
b.category == 3 ||
b.category == 8 ||
b.category == 9 ||
b.category == 10 ||
b.category == 11 ||
b.category == 12)
.toList();
final Book result = await showSearch(
context: context,
delegate: BooksSearch(booksSearchData));
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text(result.name)));
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => result.category == 1
? BookDescription(storyBooksValue: result)
: PdfScreen(singleBookData: result)),
);
},
);
}),
And here is my search delegate code -
import 'package:flutter/material.dart';
import 'package:boimarket/model/model.dart';
class BooksSearch extends SearchDelegate<Book> {
BooksSearch(this.allBooksData);
final List<Book> allBooksData;
#override
List<Widget> buildActions(BuildContext context) {
return [
SizedBox(
width: 5.0,
),
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = '';
},
)
];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
close(context, null);
},
);
}
#override
Widget buildResults(BuildContext context) {
return Container(
child: null,
color: Colors.black,
);
}
#override
Widget buildSuggestions(BuildContext context) {
return FutureBuilder(
future: fetchBooks(),
builder: (context, AsyncSnapshot<List<Book>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Check Your Internet connection');
case ConnectionState.waiting:
return Center(
child: Text('Please Wait'),
);
case ConnectionState.active:
return Text('');
case ConnectionState.done:
if (!snapshot.hasData) {
return Center(
child: Text('No Data'),
);
} else if (snapshot.hasError) {
print(snapshot.error);
return Text('${snapshot.error}');
} else {
print("query $query");
final List<Book> result = allBooksData
.where((a) =>
a.name.toLowerCase().contains(query.toLowerCase()) ||
a.author.toLowerCase().contains(query.toLowerCase()) ||
a.genreClass
.toLowerCase()
.contains(query.toLowerCase()))
.toList();
result.sort((a, b) => a.name.compareTo(b.name));
return ListView(
children: result
.map<ListTile>((a) => ListTile(
leading: FadeInImage.assetNetwork(
fadeOutCurve: Curves.easeInCubic,
placeholder: 'assets/images/bookshelf.jpg',
image: a.imgUrl == null
? 'assets/images/bookshelf.jpg'
: a.imgUrl,
fit: BoxFit.cover,
),
title: Text(
a.name,
overflow: TextOverflow.fade,
),
subtitle: Text(
a.author,
overflow: TextOverflow.visible,
),
trailing: Text(
a.genreClass,
overflow: TextOverflow.clip,
),
onTap: () {
close(context, a);
},
))
.toList());
}
}
});
}
}
It's beacause of this code.
#override
Widget buildResults(BuildContext context) {
return Container(
child: null,
color: Colors.black,
);
}
From the docs
buildResults(BuildContext context) → Widget The results shown after
the user submits a search from the search page.
Reference
To solve this issue make sure you return the results instead of a black Container. You probably want to add a ListView and populate it with data.