How can I send a string from another route to a route that has the same block?
my problem is dataLoading because when I send data from another route I still have DataLoadingState so as I speak I need to send a string to another route which has the same block
I have already tried everything, please help
firstPage
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Barcode Scanner')),
body: BlocProvider<ScannerBloc>(
create: (context) => ScannerBloc(), child: MainPageView()),
);
}
}
class MainPageView extends StatefulWidget {
MainPageView({Key? key}) : super(key: key);
#override
State<MainPageView> createState() => _MainPageViewState();
}
class _MainPageViewState extends State<MainPageView> {
#override
Widget build(BuildContext context) {
return Column(
children: [
BlocBuilder<ScannerBloc, ScannerState>(
builder: (context, state) {
if (state is DataLoading) {
return Center(
child: Text("Loading Data..."),
);
}
if (state is DataLoaded) {
if (state.barCode.isEmpty) {
return Center(
child: Text(' EMPTY'),
);
}
return Center(
child: Text(state.barCode),
);
// ListView.builder(
// shrinkWrap: true,
// itemCount: state.barCode.length,
// itemBuilder: (context, index) {
// return state.barCode;
// },
// );
}
if (state is DataError) {
return Center(
child: Text(state.error),
);
}
return throw Exception('something bad happen ');
},
),
BlocBuilder<ScannerBloc, ScannerState>(builder: (context, state) {
return ElevatedButton(
child: const Text('Add BarCode'),
onPressed: () {
// context.read<ScannerBloc>().add(GetBarCode(barCode: 'TEST')); <<-- THAT WORKING BUT I WANT THE SAME RESULT IN ANOITHER ROUTE
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ScannerPagee(),
),
);
});
})
],
);
}
}
secondPage
BlocBuilder<ScannerBloc, ScannerState>(builder: (context, state) {
return ElevatedButton(
child: const Text('send BarCode'),
onPressed: () {
BlocProvider.of<ScannerBloc>(context).add(
GetBarCode(barCode: 'ExampleoFBarCode'),
);
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => MainPage(),
),
);
});
})
Related
I have an app that presents an AuthScreen and then works with subsequent screens based on the Auth provider class result.
In fact, in the main.dart file I present different pages based on the provider class Auth.
If the user performs the login (and he is Supplier), he will be directed to SupplierOverviewScreen, where he can see all his events.
If he clicks on an event, he will be directed to the EditEventScreen class, where he can modify the event.
Both SupplierOverviewScreen and EditEventScreen use the same drawer (SupplierDrawer), which allows to perform the logout operation.
When peforming a logout, Auth info will be deleted and so the main.dart file (consuming Auth provider class) will present againt the AuthScreen page.
If I'm on the SupplierOverviewScreen and I open the drawer to logout, everything works.
The problem is that if I'm on the EditEventScreen and I try to logout, The screen remains stuck and the drawer doesn't pop out.
I see that under the hood everything works, and the main.dart file returns the AuthScreen exactly as it does in SupplierOverviewScreen (where it works), but nothing changes on the screen.
If I return to previous screen, it doesn't direct me to SupplierOverview screen, but to AuthScreen.
I've found two possible hacks, but I'm not satisfied with them:
Remove drawer from EditEventScreen
Change the drawer such that after the logout it performs Navigator.pushReplacementNamed(context, "/"); It works, but since main.dart file consumes Auth provider class, I have that the home is called twice (one when Auth provider class notify listeners and one when drawer calls Navigator.pushReplacementNamed(context, "/")
Do you have any idea to suggest? Thanks a lot
This is the main.dart file:
Future<void> main() async {
await dotenv.load(fileName: Environment.fileName);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
//Init state operations
}
void _configureAmplify() async {
// Amplify initial configurations
}
List<Event> _mockEvents() {
// mocked list of event objects
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => Auth()),
ChangeNotifierProxyProvider<Auth, Supplier>(
create: (ctx) => Supplier(userId: null, username: null),
update: (ctx, authData, previous) => Supplier(
userId: authData.getUserId, username: previous?.username),
),
ChangeNotifierProxyProvider<Auth, SupplierEvents>(
create: (ctx) => SupplierEvents(
userId: null,
events:
_mockEvents()),
update: (ctx, authData, previous) => SupplierEvents(
userId: authData.getUserId, events: previous?.events ?? []),
),
],
child: Consumer<Auth>(
builder: (context, authData, child) => MaterialApp(
title: 'Apperò',
theme: ThemeData(
colorScheme: Theme.of(context).colorScheme.copyWith(),
),
home: !authData.isAuth()
? FutureBuilder(
future: authData.tryAutoLogin(),
builder: (ctx, authResultSnapshot) {
print(authResultSnapshot.connectionState.name);
if (authResultSnapshot.connectionState ==
ConnectionState.waiting) {
return LoadingScreen();
} else
return AuthScreen();
})
: (authData.getUserType == UserType.supplier
? SupplierOverviewScreen()
: CustomerOverviewScreen()),
routes: {
SupplierOverviewScreen.ROUTE_NAME: (ctx) =>
SupplierOverviewScreen(),
CustomerOverviewScreen.ROUTE_NAME: (ctx) =>
CustomerOverviewScreen(),
EditEventScreen.ROUTE_NAME: (ctx) => EditEventScreen(),
}),
),
);
}
}
This is the SupplierOverviewScreen class:
class SupplierOverviewScreen extends StatefulWidget {
static const String ROUTE_NAME = '/supplier-overview-screen';
const SupplierOverviewScreen({Key? key}) : super(key: key);
#override
State<SupplierOverviewScreen> createState() => _SupplierOverviewScreenState();
}
class _SupplierOverviewScreenState extends State<SupplierOverviewScreen> {
late Future _obtainedInfo;
Future<void> _fetchInfo() async {
await Provider.of<Supplier>(context, listen: false).fetchSupplierByUserId();
await Provider.of<SupplierEvents>(context, listen: false)
.fetchEventsByUserId();
}
#override
void initState() {
_obtainedInfo = _fetchInfo();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Your page'), actions: []),
drawer: SupplierDrawer(),
body: FutureBuilder(
future: _obtainedInfo,
builder: (context, snapshot) => snapshot.connectionState ==
ConnectionState.waiting
? const Center(
child: CircularProgressIndicator(),
)
: RefreshIndicator(
onRefresh: () => _fetchInfo(),
child: Column(
children: [
Consumer<Supplier>(
builder: (context, supplierData, _) => Padding(
padding: EdgeInsets.all(8),
child: Text('Hello ${supplierData.username}'),
),
),
const SizedBox(
height: 10,
),
Consumer<SupplierEvents>(
builder: (context, supplierEventsData, _) => Expanded(
child: ListView.builder(
scrollDirection: Axis.vertical,
itemCount: supplierEventsData.events.length,
itemBuilder: (context, index) => Column(children: [
SupplierEventsItem(
event: supplierEventsData.events[index],
),
const Divider(),
]),
),
),
),
const SizedBox(
height: 10,
),
Padding(
padding: EdgeInsets.all(8),
child: Text('Bottom element'),
),
],
),
),
),
floatingActionButton: FloatingActionButton(
child: const Text('Add Event', textAlign: TextAlign.center,),
onPressed: () {
Navigator.of(context)
.pushNamed(EditEventScreen.ROUTE_NAME);
},
));
}
}
This is the EditEventScreen class:
class EditEventScreen extends StatefulWidget {
static const String ROUTE_NAME = '/edit-event-screen';
const EditEventScreen({Key? key}) : super(key: key);
#override
State<EditEventScreen> createState() => _EditEventScreenState();
}
class _EditEventScreenState extends State<EditEventScreen> {
late Future _obtainedInfo;
Event? inputEvent;
var _isLoading = false;
var _form = GlobalKey<FormState>();
var _titleFocusNode = FocusNode();
var _descriptionFocusNode = FocusNode();
Map<String, dynamic?> _initialValuesMap = {
//....
};
Event event = Event(// ...);
#override
void didChangeDependencies() {
_obtainedInfo = _fetchInfo();
super.didChangeDependencies();
}
#override
void dispose() {
// ...
}
Future<void> _fetchInfo() async {
try {
var eventId = ModalRoute.of(context)!.settings.arguments as int?;
print('eventId: $eventId');
if (eventId != null) {
inputEvent = await Provider.of<SupplierEvents>(context, listen: false)
.findById(eventId);
_initFormFields();
}
} catch (error) {
print(error);
}
}
void _initFormFields() {
// init form fields logic
}
Future<void> _saveForm() async {
//Form saving logic
}
#override
Widget build(BuildContext context) {
print('Building EditEventScreen');
return Scaffold(
appBar: AppBar(title: Text('Manage event'), actions: [
IconButton(
onPressed: () {
_saveForm();
},
icon: Icon(Icons.check))
]),
drawer: SupplierDrawer(),
body: FutureBuilder(
future: _obtainedInfo,
builder: (context, snapshot) => snapshot.connectionState ==
ConnectionState.waiting
? const Center(
child: CircularProgressIndicator(),
)
: _isLoading
? const Center(
child: CircularProgressIndicator(),
)
: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _form,
child: SingleChildScrollView(
child: Column(
children: [
// TextFormFields ...
],
),
),
),
),
),
);
}
}
This is the drawer class:
class SupplierDrawer extends StatelessWidget {
Future<void> logout(BuildContext context) async {
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Logout'),
content: Container(
alignment: Alignment.center,
height: 300,
width: 300,
child: Column(
children: const [
Text('Logging out...'),
Center(
child: CircularProgressIndicator(),
)
],
),
),
);
},
);
await Provider.of<Auth>(context, listen: false).signOutCurrentUser(false);
Navigator.of(context).pop();
}
#override
Widget build(BuildContext context) {
return Drawer(
child: Column(
children: [
AppBar(
title: Text('Your options'),
automaticallyImplyLeading: false,
),
Divider(),
ListTile(
leading: Icon(Icons.logout),
title: Text('Logout'),
onTap: () async {
await logout(context);
},
),
],
),
);
}
}
I have managed to acquire and display data using CloudFireStore, but I would like to send the acquired data to the destination screen when the screen is transferred.
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NextPage('test')),
));
but I don't know which function to put in.
↓This code is the one that sends the data.
class Rigakubu extends StatefulWidget {
const Rigakubu({Key? key}) : super(key: key);
#override
State<Rigakubu> createState() => _RigakubuState();
}
class _RigakubuState extends State<Rigakubu> {
final _firestore = FirebaseFirestore.instance;
List<DocumentSnapshot> documentList = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('理学部'),),
body: SafeArea(
child: StreamBuilder(
stream: _firestore.collection('理学部').snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Center(
child:Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.do_disturb_on_outlined,size: 150,),
Text(
'校外のメールアドレスでログインしているため\nこの機能は利用できません。',
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
],
)
);
}
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator()
);
}
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index){
return ListTile(
title: Text(snapshot.data!.docs[index].get('zyugyoumei')),
subtitle: Text(snapshot.data!.docs[index].get('kousimei')),
onTap: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => View(post: snapshot.data!.docs)),
);
}
);
}
);
},
)
),
);
}
}
↓This code is the code on the receiving end of the data.
import 'package:flutter/material.dart';
import 'package:http/http.dart';
class View extends StatefulWidget {
final int post;
View(this.post);
const View({Key? key, required this.post}) : super(key: key);
#override
State<View> createState() => _ViewState();
}
class _ViewState extends State<View> {
late int state;
#override
void initState() {
super.initState();
// 受け取ったデータを状態を管理する変数に格納
state = widget.post;
}
#override
Widget build(BuildContext context) {
return Container(
child: Text(widget.post.bumon),
);
}
}
You could probably try this inside the onTap function before calling the Navigator
documentList = snapshot.data!.docs;
But generally avoid passing data through constructor and try to work with state management.
I can't figure out why the TodoList doesn't load immediately when I enter the app. Instead of a list, I get a TodoEmptyState status, even if I already have saved items in the DB, it's still an empty list at first. But after that, when I add a new element and update it, then it will load.
But I want the list to be immediately loaded (if there are saved items) when entering the application without clicking on the buttons. Please tell me how to do it?
home_screen
Widget build(BuildContext context) {
return BlocBuilder<TodoBloc, TodoState>(builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Todos'),
),
body: SingleChildScrollView(
child: Column(
children: [
TodoList(),
],
),
),
todo_list
class TodoList extends StatelessWidget {
#override
Widget build(BuildContext context) {
final TodoBloc todoBloc = context.read<TodoBloc>();
return BlocBuilder<TodoBloc, TodoState>(builder: (context, state) {
if (state is TodoEmptyState) {
return const Center(
child: Text(
'No Todo',
style: TextStyle(fontSize: 20.0),
),
);
}
if (state is TodoLoadingState) {
return const Center(child: CircularProgressIndicator());
}
if (state is TodoLoadedState) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: state.loadedUser.length,
itemBuilder: (context, index) =>
// return _buildListTile(state.loadedUser[index]);
ListTile(
title: Column(children: [
Text('${state.loadedUser[index].description}'),
Text('${state.loadedUser[index].id}'),
]),
trailing: IconButton(
onPressed: () {
todoBloc.add(DeleteTodos(id: state.loadedUser[index].id));
todoBloc.add(LoadTodos());
},
icon: const Icon(Icons.delete),
),
));
}
return const SizedBox.shrink();
});
}
}
todo_bloc
class TodoBloc extends Bloc<TodoEvent, TodoState> {
final TodoRepository todoRepository;
TodoBloc(this.todoRepository) : super(TodoEmptyState()) {
on<LoadTodos>((event, emit) async {
emit(TodoLoadingState());
try {
final List<Todo> _loadedTodoList = await todoRepository.getAllTodos();
emit(TodoLoadedState(loadedUser: _loadedTodoList));
} catch (_) {
emit(TodoErrorState());
}
});
Here is the screen when entering the application, but I already have data in the DB, but they were not immediately displayed. Why?
Right now the only place you add any event to your bloc is inside onPressed of the IconButton which is why your bloc state remains in its initial state TodoEmptyState and doesn't change until the IconButton is pressed. You can convert your TodoList to a StatefulWidget and add the LoadTodos event to the bloc in initState:
class TodoList extends StatefulWidget {
#override
State<TodoList> createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
#override
void initState() {
super.initState();
todoBloc.add(LoadTodos());
}
#override
void dispose() {
todoBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
final TodoBloc todoBloc = context.read<TodoBloc>();
return BlocBuilder<TodoBloc, TodoState>(builder: (context, state) {
if (state is TodoEmptyState) {
return const Center(
child: Text(
'No Todo',
style: TextStyle(fontSize: 20.0),
),
);
}
if (state is TodoLoadingState) {
return const Center(child: CircularProgressIndicator());
}
if (state is TodoLoadedState) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: state.loadedUser.length,
itemBuilder: (context, index) =>
// return _buildListTile(state.loadedUser[index]);
ListTile(
title: Column(children: [
Text('${state.loadedUser[index].description}'),
Text('${state.loadedUser[index].id}'),
]),
trailing: IconButton(
onPressed: () {
todoBloc.add(DeleteTodos(id: state.loadedUser[index].id));
todoBloc.add(LoadTodos());
},
icon: const Icon(Icons.delete),
),
));
}
return const SizedBox.shrink();
});
}
}
I'm new in Flutter. I'm trying to push a List from NewData to FillData screen with pushNamed. But it said:
The following _TypeError was thrown while handling a gesture:
type 'FillData' is not a subtype of type 'List'
If i remove the comment in '/FillData', i receive null data instead. What should i do?
This is my code:
SettingNavigator
class SettingNavigator extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => Home(),
'/NewData': (context) => NewData(),
// '/FillData': (context) => FillData(), (in comment)
}
onGenerateRoute: (setting) {
if (setting.name == '/FillData') {
final ChartGroupData chartName = setting.arguments;
final List<ChartGroupData> groupNames = setting.arguments;
return MaterialPageRoute(builder: (context) {
return FillData(
chartName: chartName,
gName: groupNames,
);
});
}
return null;
},
);
}
}
NewData
import 'package:flutter/material.dart';
class NewData extends StatefulWidget {
List<ChartGroupData> groupNames;
NewData({Key key, #required this.groupNames}) : super(key: key);
#override
NewDataStage createState() => NewDataStage();
}
class NewDataStage extends State<NewData> {
TextEditingController _nameCtrl = new TextEditingController();
var textFields = <Widget>[];
var groupTECs = <TextEditingController>[];
#override
void initState() {
super.initState();
textFields.add(createCustomTextField());
}
Widget createCustomTextField() {
var groupCtrl = TextEditingController();
groupTECs.add(groupCtrl);
return Container(
padding: EdgeInsets.fromLTRB(0, 5, 0, 0),
child: Row(
children: <Widget>[
Expanded(flex: 3, child: Text("Group ${textFields.length}")),
Container(
constraints: BoxConstraints.tightFor(width: 120, height: 60),
child: TextField(
controller: groupCtrl,
),
),
],
),
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Center(child: Text("New Chart")),
),
body: Container(
alignment: AlignmentDirectional.center,
constraints: BoxConstraints.expand(),
child: Column(
children: <Widget>[
Text(
"Your chart name",
style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
),
TextField(
style: TextStyle(fontSize: 20),
controller: _nameCtrl,
),
Expanded(
flex: 3,
child: Container(
child: ListView.builder(
shrinkWrap: true,
itemCount: textFields.length,
itemBuilder: (BuildContext context, int index) {
return textFields[index];
},
),
),
),
SizedBox(
height: 60,
width: 120,
child: RaisedButton(
onPressed: _onTapNext,
child: Text("NEXT"),
color: Colors.green,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _onTapCreate,
child: Icon(Icons.add, color: Colors.white),
shape: CircleBorder(),
),
),
);
}
void _onTapNext() {
/// Push Groups name to FillData
widget.groupNames = List<ChartGroupData>();
for (int i = 0; i < textFields.length; i++) {
var name = groupTECs[i].text;
widget.groupNames.add(ChartGroupData(name));
}
print(widget.groupNames.toString());
Navigator.pushNamed(context, '/FillData',
arguments: FillData(
gName: widget.groupNames,
chartName: ChartGroupData(_nameCtrl.text),
));
}
void _onTapCreate() {
setState(() {
textFields.add(createCustomTextField());
});
}
}
FillData
class FillData extends StatefulWidget {
final ChartGroupData chartName;
final List<ChartGroupData> gName;
FillData({Key key, #required this.chartName, #required this.gName})
: super(key: key);
#override
FillDataStage createState() => FillDataStage();
}
class FillDataStage extends State<FillData> {
void _showDialog() {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Received Data"),
content: Text(widget.chartName.toString()),
);
},
);
}
void _onTapPrintReceivedData() {
print(widget.gName);
print(widget.chartName);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Center(
child: Text("Fill your Data"),
),
),
body: Center(
child: RaisedButton(
onPressed: () {
_onTapPrintReceivedData();
_showDialog();
},
child: Text("Print Data"),
),
),
));
}
}
Class ChartGroupData
lass ChartGroupData {
final String groupNames;
ChartGroupData(this.groupNames);
#override
String toString() {
return 'Group: $groupNames';
}
}
You have 2 problems with your code:
1- you cant user routes with onGenerateRoute, because now the app doesn't know where to go, to the widget that you didn't pass anything to (inside routes) or to the widget inside the onGenerateRoute.
2- arguments is a general object that you can put whatever you want inside of it, and doing this:
final ChartGroupData chartName = setting.arguments; final
List groupNames = setting.arguments;
passes the same value to two different objects, I solved this by doing the following (it's not the best but will give you a rough idea of what you should do)
created a new object that contains the data to be passed:
class ObjectToPass {
final ChartGroupData chartName;
final List<ChartGroupData> groupNames;
ObjectToPass({this.chartName, this.groupNames});
}
changed FillData implementation:
class FillData extends StatefulWidget {
final ObjectToPass objectToPass;
FillData({Key key, #required this.objectToPass}) : super(key: key);
#override
FillDataStage createState() => FillDataStage();
}
...
void _showDialog() {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Received Data"),
content: Text(widget.objectToPass.chartName.toString()),
);
},
);
}
void _onTapPrintReceivedData() {
print(widget.objectToPass.groupNames);
print(widget.objectToPass.chartName);
}
to navigate to FillData you would:
Navigator.pushNamed(
context,
'/FillData',
arguments: ObjectToPass(
chartName: ChartGroupData(_nameCtrl.text),
groupNames: groupNames,
),
);
finally this is how your MaterialApp should look like:
return MaterialApp(
initialRoute: '/NewData',
onGenerateRoute: (setting) {
if (setting.name == '/FillData') {
return MaterialPageRoute(builder: (context) {
return FillData(
objectToPass: setting.arguments,
);
});
} else if (setting.name == '/NewData') {
return MaterialPageRoute(builder: (_) => NewData());
}
return null;
},
);
you can pass a list instead of the object I created and get your objects from it by it's index.
I have created a Flutter application with a list. On tap of an item, I am opening detail of that item.
The problem is whenever I come back from the detail screen, the list screen is reloaded. I don't want to reload the list every time.
I have used BloC architecture in this.
Below are the code snippets. Please suggest.
Thank You.
Main
void main() {
final userRepository = UserRepository();
ApiClient apiClient = ApiClient(httpClient: http.Client());
runApp(BlocProvider<AuthenticationBloc>(
builder: (context) {
return AuthenticationBloc(
userRepository: userRepository, apiClient: apiClient)
..dispatch(AppStarted());
},
child: MyApp(userRepository: userRepository),
));
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
final UserRepository userRepository;
MyApp({Key key, #required this.userRepository}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
bloc: BlocProvider.of<AuthenticationBloc>(context),
builder: (context, state) {
if (state is AuthenticationUninitialized) {
return SplashPage();
}
if (state is AuthenticationAuthenticated) {
return HomePage(userRepository: userRepository);
}
if (state is AuthenticationUnauthenticated) {
return LoginPage(userRepository: userRepository);
}
if (state is AuthenticationLoading) {
return LoadingIndicator();
}
return null;
},
),
);
}
}
List Screen
class HomePage extends StatelessWidget {
UserRepository userRepository;
HomePage({#required this.userRepository}) : super();
#override
Widget build(BuildContext context) {
ApiClient apiClient = ApiClient(httpClient: http.Client());
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
drawer: AppDrawer(userRepository),
body: BlocProvider(
builder: (context) {
return HomeBloc(apiClient);
},
child: _HomeContent(),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
backgroundColor: Colors.amberAccent,
),
);
}
}
class _HomeContent extends StatelessWidget {
#override
Widget build(BuildContext context) {
final HomeBloc homeBloc = BlocProvider.of<HomeBloc>(context);
homeBloc.dispatch(FetchMovieList());
return BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) {
if (state is MovieListLoading) {
return Center(
child: CircularProgressIndicator(),
);
}
if (state is MovieListLoaded) {
List<Movie> topRatedMovies = state.movieList;
return new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new ListTile(
title: Card(
child: Row(
children: <Widget>[
Image.network(ApiClient.IMAGE_BASE_URL +
topRatedMovies[index].poster_path),
Column(
children: <Widget>[
Text(topRatedMovies[index].title),
],
)
],
),
),
onTap: () {
_onListItemTapped(topRatedMovies[index].id, context);
},
);
},
itemCount: topRatedMovies.length,
);
}
if (state is MovieListError) {
return Center(
child: Text('Error in calling API'),
);
}
return Center(child: Text('Employee data not found'));
},
);
}
void _onListItemTapped(int movieId, BuildContext context) {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => MovieDetailPage(
movieId: movieId,
)));
}
}
At anytime your build method needs to be ready for multiple build calls. If build calls are causing problem then something is probably wrong. It would be a better idea to fetch the data outside the build method to prevent unnecessary API calls.
For example you can create a Stateful Widget and in initState method you can fetch the data. After that, build method is called to prepare UI with the data. You can use a Future Builder to show progress and update UI when the data is fetched.
Example:
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Future _future;
Future getData() async {
// Fetch data
}
#override
void initState() {
super.initState();
_future = getData();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _future,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
if (snapshot.data.hasErrors) {
return Text('Error: ${snapshot.data.errors}');
} else {
// Data is fetched, build UI
return ListTile();
}
}
});
}
}