Related
the app is simple categories/products display , everything works fine except select a product from a category products and swip back to the products widget , the state changes and it's neither one of the states i created and just shows a loading indicator ( ProductsWrapper default return from state).
so here is the code :
ProductBloc :
class ProductBloc extends Bloc<ProductEvent, ProductState> {
final ProductRepository productRepository;
ProductBloc({required this.productRepository}) : super(ProductsEmpty());
#override
Stream<Transition<ProductEvent, ProductState>> transformEvents(
Stream<ProductEvent> events,
TransitionFunction<ProductEvent, ProductState> transitionFn) {
return super.transformEvents(
events.debounceTime(const Duration(microseconds: 500)), transitionFn);
}
#override
Stream<ProductState> mapEventToState(ProductEvent event) async* {
if (event is FetchProducts) {
yield* _mapFetchProductsToState(event);
} else if (event is RefreshProducts) {
yield* _mapRefreshProductsToState(event);
} else if (event is FetchProduct) {
yield* _mapFetchProductToState(event);
} else if (event is RefreshProduct) {
yield* _mapRefreshProductToState(event);
}
}
Stream<ProductState> _mapFetchProductsToState(FetchProducts event) async* {
try {
final products =
(await productRepository.getCategoryProducts(event.categoryId));
yield ProductsLoaded(products: products.products!);
} catch (_) {
yield state;
}
}
Stream<ProductState> _mapRefreshProductsToState(
RefreshProducts event) async* {
try {
final products =
await productRepository.getCategoryProducts(event.categoryId);
yield ProductsLoaded(products: products.products!);
return;
} catch (_) {
yield state;
}
}
Stream<ProductState> _mapFetchProductToState(FetchProduct event) async* {
try {
final product =
(await productRepository.getProductDetails(event.productId));
yield ProductLoaded(product: product);
} catch (e) {
yield state;
}
}
Stream<ProductState> _mapRefreshProductToState(RefreshProduct event) async* {
try {
final product =
await productRepository.getProductDetails(event.productId);
yield ProductLoaded(product: product);
return;
} catch (_) {
yield state;
}
}
}
states :
abstract class ProductState extends Equatable {
const ProductState();
#override
List<Object?> get props => [];
}
class ProductsEmpty extends ProductState {}
class ProductEmpty extends ProductState {}
class ProductLoading extends ProductState {}
class ProductsLoading extends ProductState {}
class ProductLoaded extends ProductState {
final Product product;
const ProductLoaded({required this.product});
ProductLoaded copyWith({required Product product}) {
return ProductLoaded(product: product);
}
#override
List<Object?> get props => [product];
#override
String toString() => 'ProductLoaded { product: ${product.name}}';
}
class ProductsLoaded extends ProductState {
final List<Product> products;
const ProductsLoaded({required this.products});
ProductsLoaded copyWith({required List<Product> products}) {
return ProductsLoaded(products: products);
}
#override
List<Object?> get props => [products];
#override
String toString() => 'ProductLoaded { products: ${products.length}}';
}
class ProductError extends ProductState {}
ProductRepository ( ProductApiService is just the api and it's working fine ) :
class ProductRepository {
final ProductApiService productApiService;
ProductRepository({ProductApiService? productApiService})
: productApiService = productApiService ?? ProductApiService();
Future<Products> getCategoryProducts(int? categoryId) async {
return productApiService.fetchCategoryProducts(categoryId);
}
Future<Product> getProductDetails(int? productId) async {
return productApiService.fetchProductDetails(productId);
}
}
ProductsWrapper :
final int? categoryId;
const ProductsWrapper({Key? key, required this.categoryId}) : super(key: key);
#override
_ProductsWrapperState createState() => _ProductsWrapperState();
}
class _ProductsWrapperState extends State<ProductsWrapper> {
final _scrollController = ScrollController();
final _scrollThreshold = 200;
Completer _productsRefreshCompleter = new Completer();
List<Product> products = [];
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
void _onScroll() {
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.position.pixels;
if (maxScroll - currentScroll <= _scrollThreshold) {
context
.read<ProductBloc>()
.add(FetchProducts(categoryId: widget.categoryId!));
}
}
#override
void initState() {
super.initState();
context
.read<ProductBloc>()
.add(FetchProducts(categoryId: widget.categoryId!));
_scrollController.addListener(_onScroll);
_productsRefreshCompleter = Completer();
}
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
final double itemHeight = 260;
final double itemWidth = size.width / 2;
return Scaffold(
key: _scaffoldKey,
body: BlocListener<ProductBloc, ProductState>(
listener: (context, state) {
if (state is ProductsLoaded) {
products = state.products;
_productsRefreshCompleter.complete();
}
},
child: Container(
margin: EdgeInsets.all(8.0),
child: BlocBuilder<ProductBloc, ProductState>(
builder: (context, state) {
if (state is ProductsLoading) {
print('a7a');
return Center(
child: LoadingIndicator(),
);
}
if (state is ProductsLoaded) {
products = state.products;
if (state.products.isEmpty) {
return Center(
child: Text("No Products Found in this category"),
);
}
return Scaffold(
body: SafeArea(
child: Container(
child: GridView.builder(
itemCount: products.length,
scrollDirection: Axis.vertical,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio:
(itemWidth / itemHeight)),
itemBuilder: (context, index) => Card(
elevation: 0,
child: InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
ProductDetailScreen(
productId:
products[index]
.id)));
},
child: Container(
child: Column(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
ClipRRect(
child: Image.network(
products[index]
.image!
.image
.toString(),
height: 150,
fit: BoxFit.fitWidth,
),
),
Padding(
padding: EdgeInsets.all(8.0),
child: Text(
products[index].name.toString(),
style: TextStyle(
color: Colors.black,
fontWeight:
FontWeight.bold),
),
),
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Padding(
padding: EdgeInsets.all(12.0),
child: Text(
'\$${products[index].price.toString()}'),
),
Padding(
padding: EdgeInsets.only(
right: 8.0),
child: CircleAvatar(
backgroundColor:
Theme.of(context)
.primaryColor,
radius: 10,
child: IconButton(
padding: EdgeInsets.zero,
icon: Icon(
Icons.add,
size: 20,
),
color: Colors.white,
onPressed: () {},
),
),
)
],
)
],
),
),
),
)),
),
),
);
}
return Center(
child: LoadingIndicator(strokeWidth: 5.0,),
);
}))));
}
}
ProductDetailScreen :
class ProductDetailScreen extends StatefulWidget {
final int? productId;
const ProductDetailScreen({Key? key, required this.productId})
: super(key: key);
#override
_ProductDetailScreenState createState() => _ProductDetailScreenState();
}
class _ProductDetailScreenState extends State<ProductDetailScreen> {
Completer _productRefreshCompleter = new Completer();
Product product = new Product();
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
super.initState();
context.read<ProductBloc>().add(FetchProduct(productId: widget.productId));
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: BlocListener<ProductBloc, ProductState>(
listener: (context, state) {
if (state is ProductLoaded) {
product = state.product;
_productRefreshCompleter.complete();
_productRefreshCompleter = Completer();
}
},
child: Container(
child: BlocBuilder<ProductBloc, ProductState>(
builder: (context, state) {
if (state is ProductLoading) {
return Center(
child: LoadingIndicator(),
);
}
if (state is ProductLoaded) {
return Scaffold(
body: SafeArea(
child: Container(
child: Text(product.name.toString()),
),
),
);
}
return Center(
child: LoadingIndicator(
strokeWidth: 5.0,
),
);
},
),
),
),
);
}
}
any help is appreciated .
thanks for taking time reading this , have a nice day and stay safe.
The problem is that you are using one bloc to do 2 things. The products list is an entity, the single detail is another entity. And you need to use the properties of the states as a result inside blocBuilders.
Plus, you don't need any listener and completer. The bloc pattern refreshes all when state changes.
I have created a repo with a working solution.
https://github.com/eugenioamato/categoryproducts
I decided to study the architecture of BloC. And something really worries me. I was making a login page and noticed that after calling the Emit () function, the TextField () became empty. If I understand correctly, BlocBuilder has rebuilt the entire widget. Is there a way to rebuild only the part that has changed with a single BlocBuilder? Or something smaller.
UI:
P.S:
there is a function generate Form () It returns
Column (
children: [
TextField (),
TextField (),
],
);
class MainAuth extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CustomScaffold(
resizeToAvoidBottomInset: true,
body: SafeArea(
child: BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is AuthMain) {
return mayBe(
regForm: generateForm(context.read<AuthBloc>().loginForm,
authType.login, context),
loginForm: generateForm(context.read<AuthBloc>().regForm,
authType.register, context),
login: state.type == authType.login,
);
} else {
return Container();
}
},
),
),
);
}
}
class mayBe extends StatelessWidget {
final Widget regForm;
final Widget loginForm;
final bool login;
mayBe({required this.regForm, required this.loginForm, required this.login});
#override
Widget build(BuildContext context) {
return ListenableProvider(
create: (c) {},
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
Column(
children: [
ChangedAnimationBlock(
index: login ? 0 : 1,
children: [
Builder(builder: (c) => loginForm),
regForm,
],
),
],
),
],
),
),
),
],
),
);
}
}
Auth Cubit:
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
import 'package:proj/brain/base_functions/forms_to_form.dart';
import 'package:proj/data/repositories/main_api_repository.dart';
part 'auth_state.dart';
class AuthCubit extends Cubit<AuthState> {
final MainApiRepository mainApiRepository;
authType curType = authType.register;
Map responseRegBody = {};
Map responseLoginBody = {};
AuthCubit({required this.mainApiRepository}) : super(AuthInitial()) {
start();
}
Future<void> start() async {
await getForms();
emit(
AuthMain(),
);
}
Future<void> getForms() async {
await getLoginForm();
await getRegisterForm();
}
void changeType() {
var currentState = state;
if (currentState is AuthMain) {
authType oldAuthType = curType;
authType newAuthType;
if (oldAuthType == authType.login) {
newAuthType = authType.register;
} else {
newAuthType = authType.login;
}
// curType = newAuthType;
emit(
AuthMain(type: newAuthType),
);
}
}
Map loginForm = {};
Map regForm = {};
Future<void> getLoginForm() async {
var response = await mainApiRepository.getAuthFromSettings();
loginForm = searchFormIntoForms(response);
print(response);
print(loginForm);
print("loginForm");
}
Future<void> getRegisterForm() async {
var response = await mainApiRepository.getRegistrationFormSettings();
regForm = searchFormIntoForms(response);
}
}
Auth state:
part of 'auth_cubit.dart';
enum authType {
register,
login,
}
#immutable
abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthMain extends AuthState {
final authType type;
AuthMain({
this.type = authType.register,
});
}
i'm just getting started with flutter bloc.
i try to make state but it always goes to initial. what's the solution?
BLOC
class VisiMisiBloc extends Bloc<VisiMisiEvent, VisiMisiState> {
VisiMisiBloc(this.visiMisiRepository) : super(VisiMisiInitial());
final VisiMisiRepository visiMisiRepository;
#override
Stream<VisiMisiState> mapEventToState(VisiMisiEvent event) async* {
if (event is GetVisiMisiList) {
yield* _getVisiMisi(event, state);
}
}
Stream<VisiMisiState> _getVisiMisi(VisiMisiEvent event, VisiMisiState state) async* {
yield VisiMisiLoading();
try {
ResponseModel<VisiMisiModel> response = await visiMisiRepository.getVisiMisi();
print(response);
if (response.statusCode == 0) {
int insertedId = await visiMisiRepository.insertVisiMisi(response.data);
print(insertedId);
List<VisiMisiModel> visiMisiList = await visiMisiRepository.getAllVisiMisi();
yield VisiMisiLoaded(visiMisiList);
} else {
yield VisiMisiError(response.errorMessage);
}
} on Exception catch (e) {
yield VisiMisiError(e.toString());
}
}
}
STATE
part of 'visi_misi_bloc.dart';
abstract class VisiMisiState extends Equatable {
const VisiMisiState();
}
class VisiMisiInitial extends VisiMisiState {
const VisiMisiInitial();
#override
List<Object>get props => [];
}
class VisiMisiLoading extends VisiMisiState {
const VisiMisiLoading();
#override
List<Object>get props => [];
}
class VisiMisiLoaded extends VisiMisiState {
final List<VisiMisiModel> visiMisiModel;
const VisiMisiLoaded(this.visiMisiModel);
#override
List<Object> get props => [visiMisiModel];
}
class VisiMisiError extends VisiMisiState {
final String message;
const VisiMisiError(this.message);
#override
List<Object>get props => [message];
}
EVENT
part of 'visi_misi_bloc.dart';
abstract class VisiMisiEvent extends Equatable{
const VisiMisiEvent();
}
class GetVisiMisiList extends VisiMisiEvent {
#override
List<Object> get props => [];
}
REPOSITORY
abstract class VisiMisiRepository {
Future<int> insertVisiMisi(VisiMisiModel todo);
Future<ResponseModel<VisiMisiModel>> getVisiMisi();
Future<List<VisiMisiModel>> getAllVisiMisi();
}
REPOSITORY IMPL
class VisiMisiRepositoryImpl extends VisiMisiRepository {
final NetworkInfoImpl networkInfo;
final RemoteDataSource remoteDatasource;
final VisiMisiDao dao;
VisiMisiRepositoryImpl(this.networkInfo, this.remoteDatasource, this.dao);
#override
Future<ResponseModel<VisiMisiModel>> getVisiMisi() {
return remoteDatasource.visiMisi();
}
#override
Future<int> insertVisiMisi(VisiMisiModel todo) {
return dao.upsert(todo);
}
#override
Future<List<VisiMisiModel>> getAllVisiMisi() {
return dao.getAll(userTABLE, VisiMisiModel.fromJson);
}
}
REMOTE DATASOURCE
Future<ResponseModel<VisiMisiModel>> visiMisi() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String auth_token = prefs.getString("auth_token");
try {
final response = await httpClient.get(ServiceUrl.visiMisi, auth_token);
if(response.statusCode != 200){
throw new Exception('Error getting visi misi');
}
return ResponseModel<VisiMisiModel>.fromJson(response, VisiMisiModel.fromJson);
}catch(e){
print(e);
}
}
VIEW
class VisiMisiPage extends StatefulWidget {
#override
_VisiMisiPageState createState() => _VisiMisiPageState();
}
class _VisiMisiPageState extends State<VisiMisiPage> {
VisiMisiRepository repository;
#override
void initState(){
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: AppColor.white,
appBar: AppBar(
title: Text("VISI & MISI", style: TextStyle(fontSize: 16, color: AppColor.deepCerulean),),
backgroundColor: Colors.white,
elevation: 0,
automaticallyImplyLeading: false,
brightness: Brightness.light,
leading: IconButton(
icon: new Icon(Icons.arrow_back, color: AppColor.deepCerulean,),
onPressed: () => Navigator.of(context).pop(),
),
),
body: BlocProvider<VisiMisiBloc>(
create: (_) => VisiMisiBloc(repository),
child: BlocBuilder<VisiMisiBloc, VisiMisiState>(
builder: (context, state) {
if (state is VisiMisiInitial) {
//BlocProvider.of<VisiMisiBloc>(context).add(GetVisiMisiList());
return Center(child: Text(state.toString()),);
} else if (state is VisiMisiLoading) {
return Center(child: CircularProgressIndicator(),);
} else if (state is VisiMisiLoaded) {
return SingleChildScrollView(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_visiWidget(context, state),
SizedBox(height: 20,),
_misiWidget(context),
SizedBox(height: 30,),
_footerWidget(context)
],
),
),
);
} else if (state is VisiMisiError) {
return Center(child: Text(state.message),);
}
}
)
),
)
);
}
void _onWidgetDidBuild(Function callback) {
WidgetsBinding.instance.addPostFrameCallback((_) {
callback();
});
}
}
I get an Unhandled Exception: Unhandled error NoSuchMethodError: The method 'getVisiMisi' was called on null.
in view, the state shows in VisiMisiInitial, and doesn't want to update to VisiMisiLoading
try to initialize the repository like so:
class _VisiMisiPageState extends State<VisiMisiPage> {
VisiMisiRepository repository;
#override
void initState(){
super.initState();
repository = VisiMisiRepositoryImpl( parameters here ); // add this one
}
To initialize repository, you should use RepositoryProvider.
For example, something like that
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiRepositoryProvider(
providers: [
RepositoryProvider<UserRepository>(
create: (context) => UserRepository(),
),
],
child: MultiBlocProvider(
providers: [
BlocProvider<LoginBloc>(
create: (context) => LoginBloc(context.read<UserRepository>()),
),
],
child: Widget()));
}
}
Then, it will be automatically initialized
I have analysed your code ,and I found 2 mistakes:
1st. you just created instance of your VisiMisiRepository and not initialised. and you are calling their methods that's why you getting error Unhandled error NoSuchMethodError: The method 'getVisiMisi' was called on null.
2nd. You just initialised your bloc within passing instance of repository, and haven't performed any bloc event . that's why code keep showing initial state.
You might have got your answer.
if not, take help from here it'll surely help you:
replace this:
create: (_) => VisiMisiBloc(repository),
with this:
create: (_) => VisiMisiBloc(VisiMisiRepository())..add(GetVisiMisiList()),
In your widget tree:
body: BlocProvider<VisiMisiBloc>(
create: (_) => VisiMisiBloc(VisiMisiRepository())..add(GetVisiMisiList()), //initialising bloc within repository and hit event as well.
child: BlocBuilder<VisiMisiBloc, VisiMisiState>(
builder: (context, state) {
if (state is VisiMisiInitial) {
return Center(child: Text(state.toString()),);
} else if (state is VisiMisiError) {
return Center(child: Text(state.message),);
} else if (state is VisiMisiLoaded) {
return SingleChildScrollView(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_visiWidget(context, state),
SizedBox(height: 20,),
_misiWidget(context),
SizedBox(height: 30,),
_footerWidget(context)
],
),
),
);
}
return Center(child: CircularProgressIndicator(),);
}
)
),
this answer will also resolve Unhandled error NoSuchMethodError: The method 'getVisiMisi' was called on null error.
I'm using the youtube api and placed a favorites toggle button to fill and empty the icon depending on the state and saving them in ListView in a new page called 'favorites'. Everything is working fine in the new page as I can see the favorite Icon filled, but is not being refreshed/updated in real time in the current view. If I switch to a statefulWidget, I'm able to make it work using 'setstate', but then the changes are not reflected if I empty the icons from the favorites page.
There must be something wrong in my approach as I should use the Bloc state to change both, but really stuck here. Please could you take a look at my code and give me some thoughts or ideas?
Bloc File
class MasterBloc extends Bloc<MasterEvents, MasterState> {
#override
MasterState get initialState => MasterState.initialState();
#override
Stream<MasterState> mapEventToState(MasterEvents event) async* {
if (event is MasterSetTab) {
yield this.state.copyWith(currentTab: event.tab);
} else if (event is MasterAddToHistory) {
yield* _addToHistory(event);
} else if (event is MasterRemoveFromHistory) {
yield* _removeFromHistory(event);
} else if (event is MasterToggleInFavorites) {
yield* _toggleInFavorites(event);
} else if (event is MasterLogout) {
yield this.state.copyWith(history: [], currentTab: 0);
}
}
Stream<MasterState> _addToHistory(MasterAddToHistory event) async* {
final int index = this
.state
.history
.indexWhere((item) => item.videoId == event.youtubeVideo.videoId);
if (index == -1) {
final history = List<YoutubeVideo>.from(this.state.history);
history.add(event.youtubeVideo);
yield this.state.copyWith(history: history);
}
}
Stream<MasterState> _removeFromHistory(MasterRemoveFromHistory event) async* {
final history = List<YoutubeVideo>.from(this.state.history);
history.removeAt(event.index);
yield this.state.copyWith(history: history);
}
Stream<MasterState> _toggleInFavorites(MasterToggleInFavorites event) async* {
final int index = this
.state
.favorites
.indexWhere((item) => item.videoId == event.youtubeVideo.videoId);
if (index == -1) {
final favorites = List<YoutubeVideo>.from(this.state.favorites);
favorites.add(event.youtubeVideo);
event.youtubeVideo.isFavorite = true;
yield this.state.copyWith(favorites: favorites);
} else {
final favorites = List<YoutubeVideo>.from(this.state.favorites);
favorites.removeAt(index);
event.youtubeVideo.isFavorite = false;
yield this.state.copyWith(favorites: favorites);
}
}
}
Bloc State
class MasterState extends Equatable {
final int currentTab;
final List<YoutubeVideo> history;
final List<YoutubeVideo> favorites;
MasterState(
{#required this.currentTab, #required this.history, this.favorites});
static MasterState initialState() =>
MasterState(currentTab: 0, history: [], favorites: []);
MasterState copyWith(
{int currentTab,
List<YoutubeVideo> history,
List<YoutubeVideo> favorites}) {
return MasterState(
currentTab: currentTab ?? this.currentTab,
history: history ?? this.history,
favorites: favorites ?? this.favorites);
}
#override
List<Object> get props => [currentTab, history, favorites];
}
BloC Events
import 'package:documentales_app/models/youtube_video.dart';
abstract class MasterEvents {}
class MasterSetTab extends MasterEvents {
final int tab;
MasterSetTab(this.tab);
}
class MasterAddToHistory extends MasterEvents {
final YoutubeVideo youtubeVideo;
MasterAddToHistory(this.youtubeVideo);
}
class MasterRemoveFromHistory extends MasterEvents {
final int index;
MasterRemoveFromHistory(this.index);
}
class MasterToggleInFavorites extends MasterEvents {
final YoutubeVideo youtubeVideo;
MasterToggleInFavorites(this.youtubeVideo);
}
class MasterLogout extends MasterEvents {}
Favorites Tab
class FavsTab extends StatefulWidget {
#override
_FavsTabState createState() => _FavsTabState();
}
class _FavsTabState extends State<FavsTab> {
#override
Widget build(BuildContext context) {
final bloc = BlocProvider.of<MasterBloc>(context);
return BlocBuilder<MasterBloc, MasterState>(
builder: (_, state) {
if (state.favorites.length == 0) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset('assets/icons/empty.svg',
width: 50, color: Colors.greenAccent),
SizedBox(
height: 5,
),
Text(
'No hay favoritos ..',
style: TextStyle(
color: Colors.greenAccent,
fontWeight: FontWeight.bold,
fontSize: 20),
)
],
),
);
}
return ListView.builder(
itemBuilder: (_, index) {
final YoutubeVideo item = state.favorites[index];
return YoutubeVideoItem(
item: item,
onDismissed: () {
bloc.add(MasterToggleInFavorites(item));
},
);
},
itemCount: state.favorites.length,
);
},
condition: (prevState, newState) =>
prevState.favorites.length != newState.favorites.length,
);
}
}
Home Tab where the state is called
class HomeTab extends StatefulWidget {
#override
_HomeTabState createState() => _HomeTabState();
}
class _HomeTabState extends State<HomeTab> {
AccountApi _accountApi = AccountApi();
YoutubeApi _youtubeApi = YoutubeApi(apiKey: API_KEY);
List<dynamic> _users = [];
List<PlayList> _playlists = [];
List<YoutubeVideo> _newVideos = [];
bool _isLoading = true;
#override
void initState() {
super.initState();
_load();
}
_load() async {
final users = await _accountApi.getUsers(1);
final List<PlayList> playLists =
await _youtubeApi.getPlaylists('UCCMksip5JfLMW4AJGsjTYUA');
final List<YoutubeVideo> newVideos = await _youtubeApi
.getPlaylistVideos('PLFXLg_sVKmuujWVeOmrzsM1NnDFa8uoNk');
setState(() {
_users.addAll(users);
_playlists.addAll(playLists);
_newVideos.addAll(newVideos);
_isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return ListView(
children: [
_isLoading
? HomeTabShimmer()
: Column(
children: [
TopPlayLists(items: _playlists),
SizedBox(height: 10),
NewVideos(
items: _newVideos,
),
SizedBox(height: 5),
],
)
],
);
}
}
Finally the toggle button
CupertinoButton(
padding: EdgeInsets.zero,
minSize: 30,
onPressed: () {
masterBloc.add(MasterToggleInFavorites(item));
},
child: CircleContainer(
child: Icon(
//Icons.playlist_add,
item.isFavorite
? Icons.favorite
: Icons.favorite_border,
color: Colors.white,
),
size: 35,
),
),
You can update the Toggle inside a BlocBuilder - the widget rebuilds on state changes. If there's a state change that you'd like to observe in your bloc, calling something like context.read<YourBloc>().doSomething() should update the widgets inside BlocBuilder.
Using BlocListener is another approach that you can use.
I am beginner in flutter and working on fetching the specific data by using flutter_bloc package. I have successfully fetch the api data by using flutter_bloc in HomePage but how do i fetch the more specific data.For example in Home Page it fetch the data when i open the app and there is a button at the bottom which moves to new screen that is a Settings Screen which has Two radio buttons and one Raised Button named as Save.When i select any of the radiobutton and click on save button it should moves back to the homepage and calls the api and update the data which was already fetched in homepage. Below is the dart code and bloc code, it will be lengthy but hope you understand my code
Main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<PrayerBloc>(
create: (BuildContext context) => PrayerBloc(repository: PrayerRepositoryImpl()),
),
BlocProvider<MethodBloc>(
create: (BuildContext context) => MethodBloc(methodRepository: MethodRepositoryImpl()),
),
],
child: HomePage(),
);
HomePage.dart
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
PrayerBloc prayerBloc;
#override
void initState() {
super.initState();
prayerBloc = BlocProvider.of<PrayerBloc>(context);
prayerBloc.add(FetchPrayerEvent());
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) {
return Material(
child: Scaffold(
appBar: AppBar(
title: Text("Prayer API"),
),
body: Container(
child: BlocListener<PrayerBloc, PrayerState>(
listener: (context, state) {
if (state is PrayerErrorState) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
),
);
}
},
child: BlocBuilder<PrayerBloc, PrayerState>(
builder: (context, state) {
if (state is InitialPrayerState) {
return buildLoading();
} else if (state is PrayerLoadingState) {
return buildLoading();
} else if (state is PrayerLoadedState) {
return buildArticleList(state.item);
} else if (state is PrayerErrorState) {
return buildErrorUi(state.message);
}
},
),
),
),
),
);
},
),
);
}
Widget buildLoading() {
return Center(
child: CircularProgressIndicator(),
);
}
Widget buildErrorUi(String message) {
return Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
message,
style: TextStyle(color: Colors.red),
),
),
);
}
Widget buildArticleList(List<Item> item) {
return ListView.builder(
itemCount: item == null ? 0 : item.length,
itemBuilder: (BuildContext ctx, int pos) {
return new Container(
child: new Center(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: new Container(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 10.0),
),
Row(
children: <Widget>[
Text("Fajr"),
Padding(
padding: EdgeInsets.only(left: 50.0),
),
Text(item[pos].fajr),
],
),
Row(
children: <Widget>[
Text("Dhuhr"),
Padding(
padding: EdgeInsets.only(left: 30.0),
),
Text(item[pos].dhuhr),
],
),
Builder(
builder: (context)=>
RaisedButton(
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SettingsPage()),
);
},
),
)
],
),
),
),
)
],
),
),
);
},
);
}
Prayer_bloc.dart
class PrayerBloc extends Bloc<PrayerEvent, PrayerState> {
PrayerRepository repository;
PrayerBloc({#required this.repository});
#override
PrayerState get initialState => InitialPrayerState();
#override
Stream<PrayerState> mapEventToState(
PrayerEvent event,
) async* {
if (event is FetchPrayerEvent) {
yield PrayerLoadingState();
try {
List<Item> item = await repository.getItem();
yield PrayerLoadedState(item: item);
} catch (e) {
yield PrayerErrorState(message: e.toString());
}
}
}
}
PrayerEvent.dart
abstract class PrayerEvent extends Equatable {}
class FetchPrayerEvent extends PrayerEvent {
#override
// TODO: implement props
List<Object> get props => null;
}
PrayerState.dart
abstract class PrayerState extends Equatable {
const PrayerState();
}
class InitialPrayerState extends PrayerState {
#override
List<Object> get props => [];
}
class PrayerLoadingState extends PrayerState {
#override
List<Object> get props => [];
}
class PrayerLoadedState extends PrayerState {
List<Item> item;
PrayerLoadedState({#required this.item});
#override
List<Object> get props => null;
}
class PrayerErrorState extends PrayerState {
String message;
PrayerErrorState({#required this.message});
#override
List<Object> get props => [message];
}
PrayerRepository.dart
abstract class PrayerRepository {
Future<List<Item>> getItem();
}
class PrayerRepositoryImpl implements PrayerRepository {
#override
Future<List<Item>> getItem() async {
var response = await http.get("https://muslimsalat.com/riyadh.json?key=");
if (response.statusCode == 200) {
var data = json.decode(response.body);
List<Item> item = Welcome.fromJson(data).items;
return item;
} else {
throw Exception();
}
}
}
So these dart code fetch the data from api and load in HomePage when i open the application.Now the second page which is settings page, below is the code
SettingsPage.dart
class SettingsPage extends StatefulWidget {
#override
_SettingsPageState createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
int selectedRadio;
#override
void initState() {
super.initState();
selectedRadio=0;
}
setSelectedRadio(int val){
setState(() {
selectedRadio=val;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: ListView(
children: <Widget>[
BlocBuilder<MethodBloc,MethodState>(
builder: (context,state){
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.all(15.0),
child: Text(
"Prayer Methods",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
Column(
children: <Widget>[
RadioListTile(
value: 1,
groupValue: selectedRadio,
activeColor: Colors.black,
title: Text(
"Egyptian General Authority of Survey",
),
onChanged: (val) {
print(val);
setSelectedRadio(val);
}),
RadioListTile(
value: 2,
groupValue: selectedRadio,
activeColor: Colors.black,
title: Text(
"University Of Islamic Sciences, Karachi (Shafi)",
),
onChanged: (val) {
print(val);
setSelectedRadio(val);
}),
FloatingActionButton(
onPressed: (){
Navigator.pop(context);
BlocProvider.of<MethodBloc>(context).add(MethodChangedEvent(method: selectedRadio)); //I have try this code in onpressed but unfortunately not succeed
print(selectedRadio);
},
child: Text('Save')
)
],
),
],
);
},
)
],
),
),
);
}
}
MethodBloc.dart
class MethodBloc extends Bloc<MethodEvent, MethodState> {
MethodRepository methodRepository;
MethodBloc({#required this.methodRepository});
#override
MethodState get initialState => InitialMethodState();
#override
Stream<MethodState> mapEventToState(
MethodEvent event,
) async* {
if(event is MethodChangedEvent){
yield MethodLoadingState();
try {
List<Item> item = await methodRepository.getMethod(event.method);
yield MethodLoadedState(item: item);
} catch (e) {
yield MethodErrorState(message: e.toString());
}
}
}
}
MethodEvent.dart
abstract class MethodEvent extends Equatable {
const MethodEvent();
}
class MethodChangedEvent extends MethodEvent {
final int method;
MethodChangedEvent({this.method}) : assert(method != null);
#override
List<Object> get props => null;
}
MethodState.dart
abstract class MethodState extends Equatable {
const MethodState();
}
class InitialMethodState extends MethodState {
#override
List<Object> get props => [];
}
class MethodLoadingState extends MethodState {
#override
List<Object> get props => [];
}
class MethodLoadedState extends MethodState {
List<Item> item;
MethodLoadedState({#required this.item});
#override
List<Object> get props => null;
}
class MethodErrorState extends MethodState {
String message;
MethodErrorState({#required this.message});
#override
List<Object> get props => [message];
}
MethodRepository.dart
abstract class MethodRepository{
Future<List<Item>> getMethod(int method);
}
class MethodRepositoryImpl implements MethodRepository {
#override
Future<List<Item>> getMethod(int method) async {
var response = await http.get("https://muslimsalat.com/riyadh/$method.json?key=");
if (response.statusCode == 200) {
var data = json.decode(response.body);
List<Item> item = Welcome.fromJson(data).items;
return item;
} else {
throw Exception();
}
}
}
PrayerModel.dart
class Welcome {
String title;
String query;
String welcomeFor;
int method;
String prayerMethodName;
String daylight;
String timezone;
String mapImage;
String sealevel;
TodayWeather todayWeather;
String link;
String qiblaDirection;
String latitude;
String longitude;
String address;
String city;
String state;
String postalCode;
String country;
String countryCode;
List<Item> items;
int statusValid;
int statusCode;
String statusDescription;
Welcome({
this.title,
this.query,
this.welcomeFor,
this.method,
this.prayerMethodName,
this.daylight,
this.timezone,
this.mapImage,
this.sealevel,
this.todayWeather,
this.link,
this.qiblaDirection,
this.latitude,
this.longitude,
this.address,
this.city,
this.state,
this.postalCode,
this.country,
this.countryCode,
this.items,
this.statusValid,
this.statusCode,
this.statusDescription,
});
factory Welcome.fromJson(Map<String, dynamic> json) => Welcome(
title: json["title"],
query: json["query"],
welcomeFor: json["for"],
method: json["method"],
prayerMethodName: json["prayer_method_name"],
daylight: json["daylight"],
timezone: json["timezone"],
mapImage: json["map_image"],
sealevel: json["sealevel"],
todayWeather: TodayWeather.fromJson(json["today_weather"]),
link: json["link"],
qiblaDirection: json["qibla_direction"],
latitude: json["latitude"],
longitude: json["longitude"],
address: json["address"],
city: json["city"],
state: json["state"],
postalCode: json["postal_code"],
country: json["country"],
countryCode: json["country_code"],
items: List<Item>.from(json["items"].map((x) => Item.fromJson(x))),
statusValid: json["status_valid"],
statusCode: json["status_code"],
statusDescription: json["status_description"],
);
Map<String, dynamic> toJson() => {
"title": title,
"query": query,
"for": welcomeFor,
"method": method,
"prayer_method_name": prayerMethodName,
"daylight": daylight,
"timezone": timezone,
"map_image": mapImage,
"sealevel": sealevel,
"today_weather": todayWeather.toJson(),
"link": link,
"qibla_direction": qiblaDirection,
"latitude": latitude,
"longitude": longitude,
"address": address,
"city": city,
"state": state,
"postal_code": postalCode,
"country": country,
"country_code": countryCode,
"items": List<dynamic>.from(items.map((x) => x.toJson())),
"status_valid": statusValid,
"status_code": statusCode,
"status_description": statusDescription,
};
}
class Item {
String dateFor;
String fajr;
String shurooq;
String dhuhr;
String asr;
String maghrib;
String isha;
Item({
this.dateFor,
this.fajr,
this.shurooq,
this.dhuhr,
this.asr,
this.maghrib,
this.isha,
});
factory Item.fromJson(Map<String, dynamic> json) => Item(
dateFor: json["date_for"],
fajr: json["fajr"],
shurooq: json["shurooq"],
dhuhr: json["dhuhr"],
asr: json["asr"],
maghrib: json["maghrib"],
isha: json["isha"],
);
Map<String, dynamic> toJson() => {
"date_for": dateFor,
"fajr": fajr,
"shurooq": shurooq,
"dhuhr": dhuhr,
"asr": asr,
"maghrib": maghrib,
"isha": isha,
};
}
class TodayWeather {
int pressure;
String temperature;
TodayWeather({
this.pressure,
this.temperature,
});
factory TodayWeather.fromJson(Map<String, dynamic> json) => TodayWeather(
pressure: json["pressure"],
temperature: json["temperature"],
);
Map<String, dynamic> toJson() => {
"pressure": pressure,
"temperature": temperature,
};
}
Once you've defined the Model for the Stream in bloc, you can easily access the object using BlocBuilder. As demonstrated on the code you've shared, you're able to build a List with buildArticleList() from the Stream. To access a specific data, you can follow a similar approach for that object.