Flutter bloc - Event only gets called 2 times inside the bloc logic - flutter

I'm having problems to send the event in the bloc. The problem I'm having is that when I send the first event the Bloc is getting called and after I recall it again with a diferrent tab and it works, but when I change to another tab the bloc doesn't get called even if the add event is sending. I have checked with the debbuger and when I call it after the 2 first it doesn't get executed the bloc.
Example where I send the event:
class ListOpportunitiesList extends StatefulWidget {
final String title;
final bool showTopIcons, showFilterMarvTypes;
final OpportunitiesListTypeEnum opportunitiesListTypeEnum;
const ListOpportunitiesList({
Key key,
#required this.title,
#required this.showTopIcons,
#required this.showFilterMarvTypes,
#required this.opportunitiesListTypeEnum,
}) : super(key: key);
#override
_ListOpportunitiesListState createState() => _ListOpportunitiesListState();
}
class _ListOpportunitiesListState extends State<ListOpportunitiesList> {
InvestmentMarvTypeEnum _investmentMarvTypeEnum;
InvestmentStatusEnum _investmentStatusEnum;
InvestmentOpportunityBloc investmentOpportunityBloc;
#override
void initState() {
super.initState();
investmentOpportunityBloc = getIt.getItInstance<InvestmentOpportunityBloc>();
this.getOpportunities();
}
// Calling the event works only the first 2 times
void getOpportunities() {
investmentOpportunityBloc.add(
InvestmentOpportunityLoadEvent(
opportunitiesListTypeEnum: this.widget.opportunitiesListTypeEnum,
investmentMarvTypeEnum: this._investmentMarvTypeEnum,
investmentStatusEnum: this._investmentStatusEnum,
),
);
}
#override
void dispose() {
super.dispose();
this.investmentOpportunityBloc?.close();
}
#override
Widget build(BuildContext context) {
final double height = MediaQuery.of(context).size.height;
final double topMargin = this.widget.showFilterMarvTypes
? height * UIConstants.marginTopPercentageWithFilter
: height * UIConstants.marginTopPercentage;
return BlocProvider<InvestmentOpportunityBloc>(
create: (context) => this.investmentOpportunityBloc,
child: ListView(
padding: EdgeInsets.zero,
children: [
CustomAppBar(
title: '${this.widget.title}',
showLogged: this.widget.showTopIcons,
),
Visibility(
visible: this.widget.opportunitiesListTypeEnum == OpportunitiesListTypeEnum.all,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: InvestmentMarvTypeEnum.values
.map(
(e) => SelectableRoundedRectangleButton(
onPressed: () {
setState(() {
this._investmentMarvTypeEnum =
this._investmentMarvTypeEnum == e ? null : e;
});
this.getOpportunities();
},
selected: e == this._investmentMarvTypeEnum,
text:
'${InvestmentOpportunityUtility.getMarvTypeString(e)}',
),
)
.toList(),
),
),
),
BlocBuilder<InvestmentOpportunityBloc, InvestmentOpportunityState>(
builder: (context, state) {
if (state is InvestmentOpportunityLoaded) {
if (state.investmentOpportunityModel.isNotEmpty) {
return Column(
children: [
for (InvestmentOpportunityModel opportunity
in state.investmentOpportunityModel)
InvestmentOpportunityCard(
investmentOpportunityModel: opportunity,
),
Visibility(
visible: !state.hasReachedMax,
child: ListBottomLoadingWidget(),
),
],
);
} else {
return AppEmptyWidget(
investmentMarvTypeEnum: this._investmentMarvTypeEnum,
topMargin: topMargin,
uiEmptyTypeEnum: UIEmptyTypeEnum.investment_opportunity,
);
}
} else if (state is InvestmentOpportunityError) {
return AppErrorWidget(
topMargin: topMargin,
apiNetworkException: state.apiError,
retryButton: () => this.getOpportunities(),
);
} else {
return AppLoadingWidget(
topMargin: topMargin,
);
}
},
),
],
),
);
}
}
Here is the bloc logic:
class InvestmentOpportunityBloc
extends Bloc<InvestmentOpportunityEvent, InvestmentOpportunityState> {
final GetInvestmentOpportunities getInvestmentOpportunities;
final GetInvestedInvestmentOpportunities getInvestedInvestmentOpportunities;
final GetFavorites getFavorites;
List<InvestmentOpportunityModel> listOpportunity;
InvestmentOpportunityBloc({
#required this.getInvestmentOpportunities,
#required this.getInvestedInvestmentOpportunities,
#required this.getFavorites,
}) : super(InvestmentOpportunityInitial());
#override
Stream<InvestmentOpportunityState> mapEventToState(
InvestmentOpportunityEvent event,
) async* {
final currentState = state;
if (event is InvestmentOpportunityLoadEvent || event is InvestmentOpportunityChangeTab) {
bool modifyType = false;
if (currentState is InvestmentOpportunityLoaded) {
switch ((event as InvestmentOpportunityLoadEvent).opportunitiesListTypeEnum) {
case OpportunitiesListTypeEnum.invested:
{
modifyType = (event as InvestmentOpportunityLoadEvent).investmentStatusEnum !=
currentState
.investmentsOpportunitiesInvestedDTO.investmentStatusEnum;
}
break;
case OpportunitiesListTypeEnum.all:
{
modifyType = (event as InvestmentOpportunityLoadEvent).investmentMarvTypeEnum !=
currentState.investmentOpportunityDTO.investmentMarvTypeEnum;
}
break;
}
if (!modifyType && this._hasReachedMax(currentState)) {
return;
}
}
InvestmentsOpportunitiesInvestedDTO investmentsOpportunitiesInvestedDTO;
InvestmentOpportunityDTO investmentOpportunityDTO;
Either<ApiNetworkException, List<InvestmentOpportunityModel>>
opportunityEither;
switch ((event as InvestmentOpportunityLoadEvent).opportunitiesListTypeEnum) {
case OpportunitiesListTypeEnum.invested:
{
if (currentState is InvestmentOpportunityLoaded && !modifyType) {
currentState.investmentsOpportunitiesInvestedDTO.pageDTO
.incrementPage();
investmentsOpportunitiesInvestedDTO =
currentState.investmentsOpportunitiesInvestedDTO;
} else {
yield InvestmentOpportunityInitial();
investmentsOpportunitiesInvestedDTO =
InvestmentsOpportunitiesInvestedDTO(
pageDTO: PageDTO(),
investmentStatusEnum: (event as InvestmentOpportunityLoadEvent).investmentStatusEnum,
);
}
opportunityEither = await this.getInvestedInvestmentOpportunities(
investmentsOpportunitiesInvestedDTO);
}
break;
case OpportunitiesListTypeEnum.all:
{
if (currentState is InvestmentOpportunityLoaded && !modifyType) {
currentState.investmentOpportunityDTO.pageDTO.incrementPage();
investmentOpportunityDTO = currentState.investmentOpportunityDTO;
} else {
yield InvestmentOpportunityInitial();
investmentOpportunityDTO = InvestmentOpportunityDTO(
pageDTO: PageDTO(),
investmentMarvTypeEnum: (event as InvestmentOpportunityLoadEvent).investmentMarvTypeEnum,
);
}
opportunityEither =
await this.getInvestmentOpportunities(investmentOpportunityDTO);
}
break;
}
yield opportunityEither.fold(
(error) => InvestmentOpportunityError(error),
(opportunities) {
if (currentState is InvestmentOpportunityLoaded &&
opportunities.isEmpty) {
return currentState.copyWith(hasReachedMax: true);
}
this.listOpportunity = currentState is InvestmentOpportunityLoaded && !modifyType
? currentState.investmentOpportunityModel + opportunities
: opportunities;
return InvestmentOpportunityLoaded(
investmentOpportunityModel: this.listOpportunity,
investmentOpportunityDTO: investmentOpportunityDTO,
investmentsOpportunitiesInvestedDTO:
investmentsOpportunitiesInvestedDTO,
hasReachedMax: BlocUtility.hasReachedMax(
opportunities.length,
investmentsOpportunitiesInvestedDTO != null
? investmentsOpportunitiesInvestedDTO.pageDTO.take
: investmentOpportunityDTO.pageDTO.take,
),
);
},
);
} else if (event is InvestmentOpportunityUpdateStatusOfInvestment &&
currentState is InvestmentOpportunityLoaded) {
currentState.investmentOpportunityModel[currentState
.investmentOpportunityModel
.indexOf(event.investmentOpportunityModel)] =
event.investmentOpportunityModel;
yield currentState.copyWith(
investmentOpportunityModel: currentState.investmentOpportunityModel,
);
}
}
bool _hasReachedMax(InvestmentOpportunityState state) {
return state is InvestmentOpportunityLoaded && state.hasReachedMax;
}
}

Related

flutter doesn't load more users don't know why

So I'm trying to load more users with pagination, it should only show 20 users and load new ones when the end of the screen is reached, but widget.loadMoreEndOfScroll gets returned null when it shouldn't, I don't know what else could be wrong, other times it worked a few times and than it stopped working and only showing 20 instead of loading new ones.
so this is the class what I use
class CAScaffold extends StatefulWidget {
const CAScaffold({
Key? key,
this.canLoadMoreEndOfScroll,
this.loadMoreEndOfScroll,
}) : super(key: key);
final bool? canLoadMoreEndOfScroll;
final Function? loadMoreEndOfScroll;
#override
State<CAScaffold> createState() => _CAScaffoldState();
}
this is the listener below
class _CAScaffoldState extends State<CAScaffold> {
ScrollController scrollUsersController = ScrollController();
final GlobalKey<ScaffoldState> _key = GlobalKey();
#override
void initState() {
scrollUsersController.addListener(() {
if(scrollUsersController.offset >= scrollUsersController.position.maxScrollExtent &&
!scrollUsersController.position.outOfRange &&
widget.canLoadMoreEndOfScroll != null && widget.canLoadMoreEndOfScroll!) {
print('endofscroll ${widget.loadMoreEndOfScroll}');
if(widget.loadMoreEndOfScroll != null) {
print('loadMoreEndOfScroll ${widget.loadMoreEndOfScroll}');
widget.loadMoreEndOfScroll!();
}
}
});
super.initState();
}
here it gets called and will call the getAllUsers function
#override
Widget build(BuildContext context) {
return CAScaffold(
canLoadMoreEndOfScroll: loadingUsers == false && allUsersLoaded == false,
loadMoreEndOfScroll: () {
getAllUsers();
newSearch = false;
},
pageTitle: 'Accounts',
child: Column(
children: <Widget>[
BlocConsumer<AccountScreenCubit, AccountScreenState>(
listener: (context, state) {
if(state is AccountScreenLoadingUsersState) {
loadingUsers = true;
}
if(state is AccountScreenUsersError) {
loadingUsers = false;
}
if(state is AccountScreenUsersLoaded) {
users.addAll(state.users);
print(state.users.length);
loadingUsers = false;
if(state.users.isEmpty) {
allUsersLoaded = true;
}
}
},
getAllUsers function
void getAllUsers() {
if(loadingUsers == false && allUsersLoaded == false) {
context.read<AccountScreenCubit>().getUsers(
searchTerm: searchInput,
orderFilter: dropDownValue,
newSearch: newSearch,
);
}
}
and than it gets send to the cubit
class AccountScreenCubit extends Cubit<AccountScreenState> {
AccountScreenCubit() : super(AccountScreenInitial());
QuerySnapshot? lastDocument;
Future<void> getUsers({String? searchTerm, bool newSearch = true, String? orderFilter}) async {
if(newSearch){
lastDocument = null;
}
try {
emit(AccountScreenLoadingUsersState());
await FirestoreUserHelper.getAllUsersOrderedByName(
20,
searchTerm: searchTerm,
orderFilter: orderFilter,
lastSnapshot: lastDocument,
).then((FirestoreReturn value) {
if(value.success) {
if(value.snapshot != null) {
lastDocument = value.snapshot;
}
emit(AccountScreenUsersLoaded(users: value.returnMain));
} else {
emit(AccountScreenUsersError(errorMsg: value.errorMessage));
}
});
} catch(e) {
emit(AccountScreenUsersError(errorMsg: 'Something went wrong: $e'));
}
}
}
if there is something I have missed let me know
You are not calling the getAllUsers function because you forgot the brackets, so add them.
loadMoreEndOfScroll: () {
getAllUsers();
newSearch = false;
},
You probably have to put CAScaffold inside BlocConsumer, otherwise parameter canLoadMoreEndOfScroll: loadingUsers == false && allUsersLoaded == false, is never updated.

Not able to use Binance_spot Package in Flutter Dart

I am trying for the past two days to Get user data from binance Api with the Binance test net key using package https://pub.dev/packages/binance_spot/example
I tried every way possible I learned as I am a beginner I am not able to get the user data from the user account
I also changed the links that work for the testnet finance API version
here is the example code snippet
import 'dart:async';
import 'package:binance_spot/binance_spot.dart';
import 'package:flutter/material.dart' hide Interval;
class BinanceScreen extends StatefulWidget {
const BinanceScreen({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<BinanceScreen> {
BinanceSpot binanceSpot = BinanceSpot(
key: "3HKUUgtwu8WFWh4q31C5bh1veQqMEkEbF07hpMrq8xnwYsWDnj0ZWgYQkvC3gnE0",
secret: "KWwvWOi4nu8s0Qsi87iJ523cfp9Jcl8mFwt2hZHptMyahhGsxnmvdURIxVa9zA74",
);
double lastClosePrice = 0;
String tradablePairs = "";
String lastEventData = "";
WsAccountUpdate? balance;
late StreamSubscription<dynamic> klineStreamSub;
late StreamSubscription<dynamic> userdataStreamSub;
#override
void initState() {
startKlineStream();
startUserdataStream();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Binance API tester"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text("Current BTC price : $lastEventData"),
// Text("Last userdataStream event : ${balance}"),
TextButton(
onPressed: getTradablePairs,
child: const Text("GET PAIRS"),
),
Expanded(
flex: 1,
child: SelectableText(
tradablePairs,
maxLines: 200,
minLines: 1,
),
),
],
),
),
);
}
void startKlineStream() {
var stream = binanceSpot.klineStream(
symbol: "BTCUSDT",
interval: Interval.INTERVAL_5m,
);
klineStreamSub = stream.listen(handleNewKline);
}
void handleNewKline(WsKlineEvent event) {
setState(() {
lastClosePrice = event.kline.close;
});
}
void startUserdataStream() async {
var response = await binanceSpot.createListenKey();
if (response.isRight) {
var stream = binanceSpot.userDataStream(listenKey: response.right);
userdataStreamSub = stream.listen(handleUserdataEvent);
} else {
lastEventData = response.left;
}
}
void handleUserdataEvent(dynamic event) {
if (event is WsAccountUpdate) {
lastEventData =
"Account update event : ${event.balances.length} balances updated";
} else if (event is WsBalanceUpdate) {
lastEventData = "Balance update event : ${event.asset} balance updated";
} else if (event is WsExecutionReport) {
lastEventData =
"Execution report event : status is ${event.orderStatus.toStr()}";
} else if (event is WsListOrderStatus) {
lastEventData =
"ListOrder update event : status is ${event.listOrderStatus}";
} else {
lastEventData = "Unknown event type : ${event.toString()}";
}
}
void getTradablePairs() async {
var response = await binanceSpot.exchangeInfo();
if (response.isLeft) {
tradablePairs = response.left;
} else {
var listSymbol = response.right.symbols.map((e) => e.symbol).toList();
tradablePairs = "";
for (var s in listSymbol) {
tradablePairs += "$s ";
}
}
}
#override
void dispose() {
klineStreamSub.cancel();
userdataStreamSub.cancel();
super.dispose();
}
}
and here is the class code snippet which I want to acess
class WsOcoOrder {
String symbol;
int orderId;
String clientOrderId;
WsOcoOrder.fromMap(Map m)
: symbol = m['s'],
orderId = m['i'],
clientOrderId = m['c'];
}
What is the Possible way to access this class and its data in the above-given code snippet
Please refer me to the solution or a Link from where I can learn and implement by myself

Why the provider sometimes does not work?

The provider has a very strange behavior, when a product is added, the isEmpty property changes, but the provider is not called, and when the product is removed, the provider is called, what is the reason for this behavior.
There is a button with a price, when pressed noInCart, the button adds a product and the text on the button changes, if there is a product, then the button has two zones inCart, the left zone deletes the product and the right one adds more, if click on the left, the button changes as needed.
class AllGoodsViewModel extends ChangeNotifier {
var _isEmpty = true;
bool get isEmpty => _isEmpty;
void detailSetting(Goods item) {
final ind = cart.value.indexWhere((element) => element.id == item.id);
if (ind != -1) {
changeButtonState(false);
} else {
changeButtonState(true);
}
}
void changeButtonState(bool state) {
_isEmpty = state;
notifyListeners();
}
}
// adds and reduces a product
void haveItem({required Goods item, required int operation}) async {
final ind = cart.value.indexWhere((element) => element.id == item.id);
if (ind == -1) {
final minCount = item.optState == 0 ? 1 : item.opt!.count;
if (item.count < minCount) {
//order.shake();
} else {
changeButtonState(false); --------- cart is not empty, not working
cart.value.add(item);
final ind = cart.value.length - 1;
cart.value.last.isOpt = item.optState == 0 ? false : true;
cart.value.last.orderCount = minCount;
cart.value = List.from(cart.value);
await SQFliteService.cart.addToCart(cart.value.last);
changeCountInCart(operation);
}
} else {
final count = cart.value[ind].orderCount;
if (count <= item.count) {} else { return; } //order.shake()
if (operation < 0 || count + operation <= item.count) {} else { return; } //order.shake()
changeButtonState(false); --------- cart is not empty, not working
cart.value[ind].orderCount += operation;
cart.value = List.from(cart.value);
await SQFliteService.cart.updateItem(cart.value[ind].id, {"orderCount":cart.value[ind].orderCount});
changeCountInCart(operation);
}
}
class _DetailGoodsPageState extends State<DetailGoodsPage> {
GlobalKey _key = GlobalKey();
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){
Provider.of<AllGoodsViewModel>(context, listen: false).detailSetting(widget.item);
});
}
#override
Widget build(BuildContext context) {
final model = Provider.of<AllGoodsViewModel>(context, listen: false);
Widget inCart(){
return GestureDetector(
onPanDown: (details) {
Goods? item = widget.item;
RenderBox _cardBox = _key.currentContext!.findRenderObject() as RenderBox;
final localPosition = details.localPosition;
final localDx = localPosition.dx;
if (localDx <= _cardBox.size.width/2) {
Goods value = cart.value.firstWhere((element) => element.id == item.id);
if (item.optState == 0 ? value.orderCount <= 1 : value.orderCount <= value.opt!.count) {
setState(() {
final ind = cart.value.indexWhere((element) => element.id == item.id);
if (ind != -1) {
model.changeButtonState(true); ------ cart is empty it works
cart.value[ind].orderCount = 0;
SQFliteService.cart.delete(cart.value[ind].id);
cart.value = List.from(cart.value)..removeAt(ind);
}
});
} else {
model.haveItem(item: item, operation: item.optState == 0 ? -1 : (-1 * value.opt!.count));
}
} else {
model.haveItem(item: item, operation: item.optState == 0 ? 1 : item.count);
}
},
child: ...
);
}
Widget noInCart(){
return Container(
width: size.width - 16.w,
margin: EdgeInsets.symmetric(vertical: 10.h),
key: _key,
child: TextButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Design.appColor),
padding: MaterialStateProperty.all(EdgeInsets.symmetric(vertical: 8.h, horizontal: 10.w)),
shape: MaterialStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.h),
))
),
onPressed: (){
Goods? item = widget.item;
model.haveItem(item: item, operation: item.optState == 0 ? 1 : item.count);
},
child: ...
),
);
}
return ScreenUtilInitService().build((context) => Scaffold(
backgroundColor: Colors.white,
body: Container(
height: 64.h,
color: Colors.white,
child: model.isEmpty ? noInCart() : inCart()
)
in order to listen to updates you must have consumers
notifylistners function orders consumers to rebuild with the new data
wrap your widget with a consumer
Consumer<yourproviderclass>(
builder: (context, yourproviderclassinstance, child) => widget,
),

Flutter: How to change state of sibling widget widget?

I have already tried the solution here. My code already depended on a class passed on a parent class.
import 'package:flutter/cupertino.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../widgets/cupertino_modal_button_row.dart';
import '../widgets/simple_widgets.dart';
import '../models/match.dart';
class AddScoreSection extends StatefulWidget {
AddScoreSection({
this.set,
this.partnerIds,
Key key,
}) : super(key: key);
final MatchSet set;
final List<String> partnerIds;
#override
_AddScoresState createState() => _AddScoresState();
}
class _AddScoresState extends State<AddScoreSection> {
String _partnerText;
#override
Widget build(BuildContext context) {
final set = widget.set;
final _hostId = FirebaseAuth.instance.currentUser.uid;
String _partnerId = set.hostTeam != null
? set.hostTeam.firstWhere(
(element) => element != FirebaseAuth.instance.currentUser.uid)
: null;
Future<String> _partnerName(String partnerId) async {
if (partnerId == null) {
return null;
}
final userData = await FirebaseFirestore.instance
.collection('users')
.doc(partnerId)
.get();
return userData['name']['full_name'];
}
print(widget.set.visitingGames);
return CupertinoFormSection(
header: const Text('Set'),
children: [
CupertinoModalButtonRow(
builder: (context) {
return CupertinoActionSheet(
title: _partnerText == null
? const Text('Select a Partner')
: _partnerText,
actions: widget.partnerIds
.map((partner) => CupertinoActionSheetAction(
child: FutureBuilder<String>(
future: _partnerName(partner),
builder: (ctx, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return SimpleWidget.loading10;
}
return Text(snapshot.data);
},
),
onPressed: () async {
final partnerTemp = await _partnerName(partner);
_partnerId = partner;
setState(() {
_partnerText = partnerTemp;
});
set.addToHostTeam = [_partnerId, _hostId];
set.addToVisitTeam = widget.partnerIds
.where((element) =>
widget.set.hostTeam.contains(element))
.toList();
print(set.hostTeam);
Navigator.of(context).pop();
},
))
.toList());
},
buttonChild:
_partnerText == null ? 'Select your Partner' : _partnerText,
prefix: 'Your Partner'),
ScoreEntryRow(
setsData: widget.set,
prefix: 'Team Score',
),
ScoreEntryRow(
prefix: 'Opponent Score',
setsData: widget.set,
isHostMode: false,
),
],
);
}
}
class ScoreEntryRow extends StatefulWidget {
ScoreEntryRow({
Key key,
#required this.setsData,
#required this.prefix,
this.isHostMode: true,
}) : super(key: key);
final MatchSet setsData;
final String prefix;
final bool isHostMode;
#override
_ScoreEntryRowState createState() => _ScoreEntryRowState();
}
class _ScoreEntryRowState extends State<ScoreEntryRow> {
#override
Widget build(BuildContext context) {
final ValueNotifier _games = widget.isHostMode
? ValueNotifier<int>(widget.setsData.hostGames)
: ValueNotifier<int>(widget.setsData.visitingGames);
List<int> scoreList = List.generate(9, (index) => index + 1);
return CupertinoModalButtonRow(
builder: (context) {
return SizedBox(
height: 300,
child: ListView.builder(
itemCount: scoreList.length,
itemBuilder: (context, i) {
return CupertinoButton(
child: Text(scoreList[i].toString()),
onPressed: () {
setState(() {
if (widget.isHostMode) {
widget.setsData.setHostGames = scoreList[i];
widget.setsData.setVisitGames = 9 - scoreList[i];
return;
}
widget.setsData.setVisitGames = scoreList[i];
widget.setsData.setHostGames = 9 - scoreList[i];
});
});
}),
);
},
buttonChild: widget.isHostMode
? widget.setsData.hostGames == null
? '0'
: widget.setsData.hostGames.toString()
: widget.setsData.visitingGames == null
? '0'
: widget.setsData.visitingGames.toString(),
prefix: widget.prefix,
);
}
}
The code for the custom class is:
class MatchSet {
MatchSet(this.setData);
final Map<String, dynamic> setData;
List<String> hostTeam;
List<String> visitingTeam;
int hostGames;
int visitingGames;
set addToHostTeam(List<String> value) {
if (setData.values.every((element) => element != null))
hostTeam = setData['host_team'];
hostTeam = value;
}
set addToVisitTeam(List<String> value) {
if (setData.values.every((element) => element != null))
visitingTeam = setData['visiting_team'];
visitingTeam = value;
}
set setHostGames(int value) {
if (setData.values.every((element) => element != null))
hostGames = setData['host_games'];
hostGames = value;
}
set setVisitGames(int value) {
if (setData.values.every((element) => element != null))
visitingGames = setData['visiting_games'];
visitingGames = value;
}
Map<List<String>, int> get matchResults {
return {
hostTeam: hostGames,
visitingTeam: visitingGames,
};
}
List<String> get winningTeam {
if (matchResults.values.every((element) => element != null)) {
return null;
}
return matchResults[hostTeam] > matchResults[visitingTeam]
? hostTeam
: visitingTeam;
}
String result(String userId) {
if (matchResults.values.every((element) => element != null)) {
return null;
}
if (winningTeam.contains(userId)) {
return 'W';
}
return 'L';
}
bool get isUpdated {
return hostGames != null && visitingGames != null;
}
}
The state does not change for the sibling as you can see in the video. I have also trying the whole button in a ValueListenableBuilder, nothing changes.
What am I doing wrong? By all my knowledge the sibling row widget should rebuild, but it is not rebuilding.
You can use multiple approaches
the solution that you point out is a callback function and it works fine ... you use a function that when it is called it would go back to its parent widget and do some stuff there(in your case setState)
but It does not seem that you are using this it.
the other one is using StateManagement. you can use GetX , Provider, RiverPod,... .
in this one, you can only rebuild the widget that you want but in the first solution, you have to rebuild the whole parent widget.
both solutions are good the second one is more professional and more efficient but i think the first one is a must-know too

Loading data on init with BLoC

New to Flutter with BLoC. Building off of a search template, looking to have data (items) load on app load instead of on state change.
The method getCrystals() returns the correct data when the search intent .isEmpty but how can it be done on app load?
crystal_repo.dart
abstract class CrystalRepo {
Future<BuiltList<Crystal>> getCrystals();
Future<BuiltList<Crystal>> searchCrystal({
#required String query,
int startIndex: 0,
});
}
crystal_repo_impl.dart
class CrystalRepoImpl implements CrystalRepo {
static const _timeoutInMilliseconds = 120000; // 2 minutes
final Map<String, Tuple2<int, CrystalResponse>> _cached = {};
///
final CrystalApi _api;
final Mappers _mappers;
CrystalRepoImpl(this._api, this._mappers);
#override
Future<BuiltList<Crystal>> searchCrystal({
String query,
int startIndex = 0,
}) async {
assert(query != null);
final crystalsResponse = await _api.searchCrystal(
query: query,
startIndex: startIndex,
);
final crystal = crystalsResponse.map(_mappers.crystalResponseToDomain);
return BuiltList<Crystal>.of(crystal);
}
#override
Future<BuiltList<Crystal>> getCrystals() async {
final crystalsResponse = await _api.getCrystals();
final crystal = crystalsResponse.map(_mappers.crystalResponseToDomain);
return BuiltList<Crystal>.of(crystal);
}
}
search_bloc.dart
class SearchBloc implements BaseBloc {
/// Input [Function]s
final void Function(String) changeQuery;
final void Function() loadNextPage;
final void Function() retryNextPage;
final void Function() retryFirstPage;
final void Function(String) toggleFavorited;
/// Ouput [Stream]s
final ValueStream<SearchPageState> state$;
final ValueStream<int> favoriteCount$;
/// Subscribe to this stream to show message like snackbar, toast, ...
final Stream<SearchPageMessage> message$;
/// Clean up resource
final void Function() _dispose;
SearchBloc._(
this.changeQuery,
this.loadNextPage,
this.state$,
this._dispose,
this.retryNextPage,
this.retryFirstPage,
this.toggleFavorited,
this.message$,
this.favoriteCount$,
);
#override
void dispose() => _dispose();
factory SearchBloc(final CrystalRepo crystalRepo, final FavoritedCrystalsRepo favCrystalsRepo,){
assert(crystalRepo != null);
assert(favCrystalsRepo != null);
/// Stream controllers, receive input intents
final queryController = PublishSubject<String>();
final loadNextPageController = PublishSubject<void>();
final retryNextPageController = PublishSubject<void>();
final retryFirstPageController = PublishSubject<void>();
final toggleFavoritedController = PublishSubject<String>();
final controllers = [
queryController,
loadNextPageController,
retryNextPageController,
retryFirstPageController,
toggleFavoritedController,
];
/// Debounce query stream
final searchString$ = queryController
.debounceTime(const Duration(milliseconds: 300))
.distinct()
.map((s) => s.trim());
/// Search intent
final searchIntent$ = searchString$.mergeWith([
retryFirstPageController.withLatestFrom(
searchString$,
(_, String query) => query,
)
]).map((s) => SearchIntent.searchIntent(search: s));
/// Forward declare to [loadNextPageIntent] can access latest state via [DistinctValueConnectableStream.value] getter
DistinctValueConnectableStream<SearchPageState> state$;
/// Load next page intent
final loadAndRetryNextPageIntent$ = Rx.merge(
[
loadNextPageController.map((_) => state$.value).where((currentState) {
/// Can load next page?
return currentState.crystals.isNotEmpty &&
currentState.loadFirstPageError == null &&
currentState.loadNextPageError == null;
}),
retryNextPageController.map((_) => state$.value).where((currentState) {
/// Can retry?
return currentState.loadFirstPageError != null ||
currentState.loadNextPageError != null;
})
],
).withLatestFrom(searchString$, (currentState, String query) =>
Tuple2(currentState.crystals.length, query),
).map(
(tuple2) => SearchIntent.loadNextPageIntent(
search: tuple2.item2,
startIndex: tuple2.item1,
),
);
/// State stream
state$ = Rx.combineLatest2(
Rx.merge([searchIntent$, loadAndRetryNextPageIntent$]) // All intent
.doOnData((intent) => print('[INTENT] $intent'))
.switchMap((intent) => _processIntent$(intent, crystalRepo))
.doOnData((change) => print('[CHANGE] $change'))
.scan((state, action, _) => action.reduce(state),
SearchPageState.initial(),
),
favCrystalsRepo.favoritedIds$,
(SearchPageState state, BuiltSet<String> ids) => state.rebuild(
(b) => b.crystals.map(
(crystal) => crystal.rebuild((b) => b.isFavorited = ids.contains(b.id)),
),
),
).publishValueSeededDistinct(seedValue: SearchPageState.initial());
final message$ = _getMessage$(toggleFavoritedController, favCrystalsRepo, state$);
final favoriteCount = favCrystalsRepo.favoritedIds$
.map((ids) => ids.length)
.publishValueSeededDistinct(seedValue: 0);
return SearchBloc._(
queryController.add,
() => loadNextPageController.add(null),
state$,
DisposeBag([
...controllers,
message$.listen((message) => print('[MESSAGE] $message')),
favoriteCount.listen((count) => print('[FAV_COUNT] $count')),
state$.listen((state) => print('[STATE] $state')),
state$.connect(),
message$.connect(),
favoriteCount.connect(),
]).dispose,
() => retryNextPageController.add(null),
() => retryFirstPageController.add(null),
toggleFavoritedController.add,
message$,
favoriteCount,
);
}
}
/// Process [intent], convert [intent] to [Stream] of [PartialStateChange]s
Stream<PartialStateChange> _processIntent$(
SearchIntent intent,
CrystalRepo crystalRepo,
) {
perform<RESULT, PARTIAL_CHANGE>(
Stream<RESULT> streamFactory(),
PARTIAL_CHANGE map(RESULT a),
PARTIAL_CHANGE loading,
PARTIAL_CHANGE onError(dynamic e),
) {
return Rx.defer(streamFactory)
.map(map)
.startWith(loading)
.doOnError((e, s) => print(s))
.onErrorReturnWith(onError);
}
searchIntentToPartialChange$(SearchInternalIntent intent) =>
perform<BuiltList<Crystal>, PartialStateChange>(
() {
if (intent.search.isEmpty) {
return Stream.fromFuture(crystalRepo.getCrystals());
}
return Stream.fromFuture(crystalRepo.searchCrystal(query: intent.search));
},
(list) {
final crystalItems = list.map((crystal) => CrystalItem.fromDomain(crystal)).toList();
return PartialStateChange.firstPageLoaded(crystals: crystalItems, textQuery: intent.search,);
},
PartialStateChange.firstPageLoading(),
(e) {
return PartialStateChange.firstPageError(error: e,textQuery: intent.search,);
},
);
loadNextPageIntentToPartialChange$(LoadNextPageIntent intent) =>
perform<BuiltList<Crystal>, PartialStateChange>();
return intent.join(
searchIntentToPartialChange$,
loadNextPageIntentToPartialChange$,
);
}
search_state.dart
abstract class SearchPageState implements Built<SearchPageState, SearchPageStateBuilder> {
String get resultText;
BuiltList<CrystalItem> get crystals;
bool get isFirstPageLoading;
#nullable
Object get loadFirstPageError;
bool get isNextPageLoading;
#nullable
Object get loadNextPageError;
SearchPageState._();
factory SearchPageState([updates(SearchPageStateBuilder b)]) = _$SearchPageState;
factory SearchPageState.initial() {
return SearchPageState((b) => b
..resultText = ''
..crystals = ListBuilder<CrystalItem>()
..isFirstPageLoading = false
..loadFirstPageError = null
..isNextPageLoading = false
..loadNextPageError = null);
}
}
class PartialStateChange extends Union6Impl<
LoadingFirstPage,
LoadFirstPageError,
FirstPageLoaded,
LoadingNextPage,
NextPageLoaded,
LoadNextPageError> {
static const Sextet<LoadingFirstPage, LoadFirstPageError, FirstPageLoaded,
LoadingNextPage, NextPageLoaded, LoadNextPageError> _factory =
Sextet<LoadingFirstPage, LoadFirstPageError, FirstPageLoaded,
LoadingNextPage, NextPageLoaded, LoadNextPageError>();
PartialStateChange._(
Union6<LoadingFirstPage, LoadFirstPageError, FirstPageLoaded,
LoadingNextPage, NextPageLoaded, LoadNextPageError>
union)
: super(union);
factory PartialStateChange.firstPageLoading() {
return PartialStateChange._(
_factory.first(
const LoadingFirstPage()
)
);
}
factory PartialStateChange.firstPageError({
#required Object error,
#required String textQuery,
}) {
return PartialStateChange._(
_factory.second(
LoadFirstPageError(
error: error,
textQuery: textQuery,
),
),
);
}
factory PartialStateChange.firstPageLoaded({
#required List<CrystalItem> crystals,
#required String textQuery,
}) {
return PartialStateChange._(
_factory.third(
FirstPageLoaded(
crystals: crystals,
textQuery: textQuery,
),
)
);
}
factory PartialStateChange.nextPageLoading() {
return PartialStateChange._(
_factory.fourth(
const LoadingNextPage()
)
);
}
factory PartialStateChange.nextPageLoaded({
#required List<CrystalItem> crystals,
#required String textQuery,
}) {
return PartialStateChange._(
_factory.fifth(
NextPageLoaded(
textQuery: textQuery,
crystals: crystals,
),
),
);
}
factory PartialStateChange.nextPageError({
#required Object error,
#required String textQuery,
}) {
return PartialStateChange._(
_factory.sixth(
LoadNextPageError(
textQuery: textQuery,
error: error,
),
),
);
}
/// Pure function, produce new state from previous state [state] and partial state change [partialChange]
SearchPageState reduce(SearchPageState state) {
return join<SearchPageState>(
(LoadingFirstPage change) {
return state.rebuild((b) => b..isFirstPageLoading = true);
},
(LoadFirstPageError change) {
return state.rebuild((b) => b
..resultText = "Search for '${change.textQuery}', error occurred"
..isFirstPageLoading = false
..loadFirstPageError = change.error
..isNextPageLoading = false
..loadNextPageError = null
..crystals = ListBuilder<CrystalItem>());
},
(FirstPageLoaded change) {
return state.rebuild((b) => b
//..resultText = "Search for ${change.textQuery}, have ${change.crystals.length} crystals"
..resultText = ""
..crystals = ListBuilder<CrystalItem>(change.crystals)
..isFirstPageLoading = false
..isNextPageLoading = false
..loadFirstPageError = null
..loadNextPageError = null);
},
(LoadingNextPage change) {
return state.rebuild((b) => b..isNextPageLoading = true);
},
(NextPageLoaded change) {
return state.rebuild((b) {
var newListBuilder = b.crystals..addAll(change.crystals);
return b
..crystals = newListBuilder
..resultText =
"Search for '${change.textQuery}', have ${newListBuilder.length} crystals"
..isNextPageLoading = false
..loadNextPageError = null;
});
},
(LoadNextPageError change) {
return state.rebuild((b) => b
..resultText =
"Search for '${change.textQuery}', have ${state.crystals.length} crystals"
..isNextPageLoading = false
..loadNextPageError = change.error);
},
);
}
#override
String toString() => join<String>(_toString, _toString, _toString, _toString, _toString, _toString);
}
search_page.dart
class SearchListViewWidget extends StatelessWidget {
final SearchPageState state;
const SearchListViewWidget({Key key, #required this.state})
: assert(state != null),
super(key: key);
#override
Widget build(BuildContext context) {
final bloc = BlocProvider.of<SearchBloc>(context);
if (state.loadFirstPageError != null) {}
// LOOKING TO HAVE items LOADED ON APP LOAD //
final BuiltList<CrystalItem> items = state.crystals;
if (items.isEmpty) {
debugPrint('items.isEmpty');
}
return ListView.builder(
itemCount: items.length + 1,
padding: const EdgeInsets.all(0),
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
debugPrint('itemBuilder');
if (index < items.length) {
final item = items[index];
return SearchCrystalItemWidget(
crystal: item,
key: Key(item.id),
);
}
if (state.loadNextPageError != null) {
final Object error = state.loadNextPageError;
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
error is HttpException
? error.message
: 'An error occurred $error',
textAlign: TextAlign.center,
maxLines: 2,
style:
Theme.of(context).textTheme.body1.copyWith(fontSize: 15),
),
SizedBox(height: 8),
RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
onPressed: bloc.retryNextPage,
padding: const EdgeInsets.all(16.0),
child: Text(
'Retry',
style: Theme.of(context).textTheme.body1.copyWith(fontSize: 16),
),
elevation: 4.0,
),
],
),
);
}
return Container();
},
);
}
}
Ended up solving this by passing in an empty query in the app.dart
home: Consumer2<FavoritedCrystalsRepo, CrystalRepo>(
builder: (BuildContext context, FavoritedCrystalsRepo sharedPref, CrystalRepo crystalRepo) {
final searchBloc = SearchBloc(crystalRepo, sharedPref);
// Do the first search to get first result on init
searchBloc.changeQuery('');
return BlocProvider<SearchBloc>(
child: SearchPage(),
initBloc: () => searchBloc,
);
},
An alternative would be to convert your search page into a StatefulWidget and then calling searchBloc.changeQuery(''); inside of initState