I want to change appbar icon real-time (action) with StreamBuilder result,
So, I use this method,
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool isOk = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
onPressed: () {},
icon: isOk ? Icon(Icons.edit) : Icon(Icons.edit_off))
],
),
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection("users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.snapshots(),
builder: (context, streamSnapshot) {
// set this -------------------------------------------------------
if (streamSnapshot.data!['newRequests'].length != 0) {
setState(() {
isOk = true;
});
}
//---------------------------------------------------------------------
return Center(
child: CustomText(
size: 60,
text: streamSnapshot.data!['newRequests'].length != 0 ? "Have" : "0",
textColor: darkblueColor,
));
},
),
);
}
}
It doesn't work. It shows error like this:
════════ Exception caught by widgets library ═══════════════════════════════════
setState() or markNeedsBuild() called during build.
The relevant error-causing widget was
StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>
Created StreamBuilder inside appBar like this, It solved my problem. #rahulVFlutterAndroid's answer helped me to do this.
AppBar(
actions: [
StreamBuilder(
stream: stream,
builder: (context, snapshot) {
return IconButton(
onPressed: () {},
icon: snapshot.data!['newRequests'].length != 0 ? Icon(Icons.edit) : Icon(Icons.edit_off))]),
},
)
],
),
You can use like below
AppBar(
actions: [
StreamBuilder(
stream: stream,
builder: (context, snapshot) {
return IconButton(
onPressed: () {},
icon: snapshot.data!['newRequests'].length != 0 ? Icon(Icons.edit) : Icon(Icons.edit_off))]),
},
)
],
),
Related
Currently I have a StreamBuilder switching between a HomePage and LandingPage depending on the current auth state. The issue I have encountered is that I cannot pop the stack to the original /landing directory on a state change. This means that when a user logs in, the AuthPage remains on the top of the stack and the StreamBuilder builds the HomePage underneath.
AuthGate
class AuthGate extends StatelessWidget {
const AuthGate({super.key});
#override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
return snapshot.hasData ? const HomePage() : const LandingPage();
},
);
}
}
LandingPage
This pushes the AuthPage to the stack.
class LandingPage extends StatelessWidget {
const LandingPage({super.key});
...
Row(
children: <Widget>[
FloatingActionButton.extended(
heroTag: UniqueKey(),
onPressed: () {
context.push('/auth');
},
label: const Text('Get started'),
),
FloatingActionButton.extended(
heroTag: UniqueKey(),
onPressed: () {
context.push('/auth');
},
label: const Text('Log in'),
),
],
)
...
}
}
Stack before auth change
Stack after auth change
Note how the AuthPage remains on the top of the stack but the Widget under StreamBuilder changes to the HomePage
(This is my first Stack question so please feel free to ask me to amend any information etc.)
If you are using GoRouter, then what you want to achieve should be done through GoRouter similarly to this:
GoRouter(
refreshListenable:
GoRouterRefreshListenable(FirebaseAuth.instance.authStateChanges()),
initialLocation: '/auth',
routes: <GoRoute>[
GoRoute(
path: '/landing',
name: 'landing',
builder: (context, state) {
return LandingPage()
},
routes: [
GoRoute(
path: 'auth',
name: 'auth',
builder: (context, state) => const AuthPage(),
),
],
),
GoRoute(
path: '/home',
name: 'home',
builder: (context, state) => const HomePage(),
),
],
errorBuilder: (context, state) {
return const Scaffold(
body: Text('404'),
);
},
redirect: (context, state) async {
final userRepo = injector.get<UserRepository>();
final user = FirebaseAuth.instance;
const authPaths = [
'/landing',
'/landing/auth',
];
bool isAuthPage = authPaths.contains(state.subloc);
if(user != null) {
if (isAuthPage) {
return '/home';
}
}
if(!isAuthPage) {
return '/auth';
}
return null;
},
);
class GoRouterRefreshListenable extends ChangeNotifier {
GoRouterRefreshListenable(Stream stream) {
notifyListeners();
_subscription = stream.asBroadcastStream().listen(
(_) {
notifyListeners();
},
);
}
late final StreamSubscription _subscription;
#override
void dispose() {
_subscription.cancel();
super.dispose();
}
}
Please also read documentation on of GoRouter.
What is the right way to place all these widgets together? When I tried to place together widget which is building the listview. separated widget and the widgets where I am creating the UI for filters, it draws only widget with filtering items but doesn't create the listview. I was searching that I should place it in the column widget and in the expanded but it also doesn't work as I want.
Here is my code:
class _ProductListState extends State<ProductList> {
#override
Widget build(BuildContext context) {
var providerGridPagination = Provider.of<ProviderGridProduct>(context);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
title: Text("Категории товаров", style: TextStyle(color: Colors.black45),),
leading: IconButton(onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const Home()),
);
}, icon: Icon(Icons.arrow_back, color: Colors.black45,),),
),
body: Column(
children: [
FiltersWidget(),
SmartRefresher(
controller: providerGridPagination.refreshController,
enablePullUp: true,
onRefresh: () async {
final result = await providerGridPagination.getProductData(isRefresh: true);
if (result) {
providerGridPagination.refreshController.refreshCompleted();
} else {
providerGridPagination.refreshController.refreshFailed();
}
},
onLoading: () async {
final result = await providerGridPagination.getProductData();
if (result) {
providerGridPagination.refreshController.loadComplete();
} else {
providerGridPagination.refreshController.loadFailed();
}
},
child: ListView.separated(
itemBuilder: (context, index) {
//final gridItems = providerGridPagination.itemgrid[index];
return ListTile(
title: Text(providerGridPagination.itemgrid[index].title!),
);
},
separatorBuilder: (context, index) => Divider(),
itemCount: providerGridPagination.itemgrid.length,
),
),
],
),
);
}
}
the error is:
The following assertion was thrown during a scheduler callback:
This widget has been unmounted, so the State no longer has a context (and should be considered defunct).
I'm using FutureProvider to fetch data from a local db with SQflite, and then render a graph in the Consumer child. However, when loading the app, during a brief period an error is shown :
The following StateError was thrown building Consumer<List<Map<String, dynamic>>>(dirty,
dependencies: [_InheritedProviderScope<List<Map<String, dynamic>>>]):
Bad state: No element
After the graph is rendered fine.
How can I catch this loading state so the error disappears and I can show a CircularProgressIndicator() ?
Parent
FutureProvider<List<Map<String, dynamic>>>(
create: (context) {
return RecordsDatabase.instance.getRecords();
},
catchError: (context, error) {
print("error: ${error.toString()}");
return [];
},
initialData: [],
child: HomeCustom(),
)
Child
#override
Widget build(BuildContext context) {
return Consumer<List<Map<String, dynamic>>>(
builder: (context, records, child) {
GraphState graph =GraphState(records: records, context: context);
return ChangeNotifierProvider<GraphState>(
create: (_) => graph,
child: Scaffold(
backgroundColor: Colors.black,
body: Stack(children: [
Center(
child: graph.records.isEmpty
? Text(
'No Records',
style: TextStyle(color: Colors.white, fontSize: 24),
)
: MyGraph()),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(right: 30, bottom: 50),
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: _setVisible,
),
),
)
]),
),
);
});
}
}
In the Consumer, check the records value first then return the appropriate widget.
Sample...
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: FutureProvider<List<Map<String, dynamic>>?>(
create: (_) => _getRecords(),
initialData: null,
catchError: (_, __) => <Map<String, dynamic>>[
{'error': 'Something went wrong'}
],
child: HomePage(),
),
);
}
Future<List<Map<String, dynamic>>> _getRecords() async {
final bool isError = false; // set to "true" to check error case
await Future<void>.delayed(const Duration(seconds: 5));
if (isError) {
throw Exception();
}
return <Map<String, dynamic>>[
<String, int>{'item': 1},
<String, String>{'itemTxt': 'one'},
];
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Consumer<List<Map<String, dynamic>>?>(
builder: (_, List<Map<String, dynamic>>? records, __) {
if (records == null) {
return const CircularProgressIndicator();
} else if (records.isNotEmpty &&
records.first.containsKey('error')) {
return Text(records.first['error'] as String);
}
return Text(records.toString());
},
),
),
);
}
}
I would like to return a list of String in my FutureBuilder but it is impossible for me because an error tells me that the lists are not a subtype of the Widget type.
Indeed the return must be of type List to be able to add it to the initialization of my list in my class in my main class
I am attaching the two classes to you as well as the error message so that you can understand.
Thank you
Class READCITEE
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class ReadCitee extends StatelessWidget {
final String docCityId;
ReadCitee(this.docCityId);
#override
Widget build(BuildContext context) {
CollectionReference cities = FirebaseFirestore.instance.collection('city');
return FutureBuilder<DocumentSnapshot>(
future: cities.doc(docCityId).get(),
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
return Text("Documents does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data!.data() as Map<String, dynamic>;
return (data["Nom"]);
}
return Text("Loading");
});
}
}
CLASSE PRINCIPALE
import 'package:ampc93/fonction/firebase_crud/add_citee.dart';
import 'package:ampc93/fonction/firebase_crud/read_citee.dart';
import 'package:ampc93/page_citee.dart';
import 'package:flutter/material.dart';
class PageVille extends StatefulWidget {
final String? titre;
PageVille(this.titre);
#override
_PageVilleState createState() => _PageVilleState();
}
class _PageVilleState extends State<PageVille> {
TextEditingController citeeController = TextEditingController();
List<dynamic> citeesList = [];
#override
void initState() {
super.initState();
citeesList.add(ReadCitee(widget.titre!));
}
Future<void> displayDialog(BuildContext context) async {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: TextField(
controller: citeeController,
textInputAction: TextInputAction.go,
decoration:
InputDecoration(hintText: "Entrez une nouvelle citée"),
),
actions: [
TextButton(
child: Text("Ok"),
onPressed: () {
AddCitee(widget.titre!, citeeController.text);
setState(() {
citeesList.add(citeeController.text);
citeeController.clear();
});
Navigator.pop(context);
}),
TextButton(
onPressed: () {
Navigator.pop(context);
citeeController.clear();
},
child: Text("Annuler"),
)
],
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.titre!),
backgroundColor: Colors.teal,
),
body: Container(
padding: EdgeInsets.all(20.0),
child: ListView.separated(
itemBuilder: (context, index) {
return ListTile(
title: citeesList[index],
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
PageCitee(citeesList[index]))),
trailing: IconButton(
icon: Icon(Icons.delete_forever),
color: Colors.red[300],
iconSize: 32,
onPressed: () {
setState(() {
citeesList.remove(citeesList[index]);
});
}));
},
separatorBuilder: (context, index) => Divider(),
itemCount: citeesList.length)),
floatingActionButton: FloatingActionButton(
onPressed: () => displayDialog(context),
child: Icon(Icons.add),
backgroundColor: Colors.teal[300],
),
);
}
}
ERROR
The following _TypeError was thrown building FutureBuilder<DocumentSnapshot<Object?>>(dirty, state: _FutureBuilderState<DocumentSnapshot<Object?>>#b2e48):
type 'List<dynamic>' is not a subtype of type 'Widget'
The relevant error-causing widget was
FutureBuilder<DocumentSnapshot<Object?>>
package:ampc93/…/firebase_crud/read_citee.dart:12
When the exception was thrown, this was the stack
#0 ReadCitee.build.<anonymous closure>
package:ampc93/…/firebase_crud/read_citee.dart:23
#1 _FutureBuilderState.build
package:flutter/…/widgets/async.dart:773
#2 StatefulElement.build
package:flutter/…/widgets/framework.dart:4612
#3 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4495
#4 StatefulElement.performRebuild
package:flutter/…/widgets/framework.dart:4667
...
Try remove the "DocumentSnapshot". Because apparently, the "FutureBuilder" are expecting "DocumentSnapshot" data type. Try remove that from the "Future Builder" and see what happens.
I've been learning Flutter and following an online tutorial regarding Provider package, at present I'm working with StreamProvider.
I have a service which connects to Firestore and returns all documents within a collection ('reports'), these documents are then mapped to my report object.
Service:
class FirestoreService {
Firestore _db = Firestore.instance;
var random = Random();
Stream<List<Report>> getReports() {
return _db.collection('reports')
.orderBy('timeStamp', descending: true)
.snapshots()
.map((snapshot) => snapshot.documents
.map((document) => Report.fromJson(document.data))
.toList());
}
Report class:
class Report {
final int temp;
final String wax;
final String line;
final String timeStamp;
Report({this.line,this.temp,this.timeStamp,this.wax});
Report.fromJson(Map<String, dynamic> parsedJson)
: temp = parsedJson['temp'],
wax = parsedJson['wax'],
line = parsedJson['line'],
timeStamp = parsedJson['timeStamp'];
}
Within main.dart I have used MultiProvider and added my StreamProvider.
main.dart:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final FirestoreService _db = FirestoreService();
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (BuildContext context) => SettingsProvider()),
StreamProvider(create: (BuildContext context) => _db.getReports(),)
],
child: MaterialApp(
title: 'Wax App',
theme: ThemeData(
primarySwatch: Colors.deepPurple,
accentColor: Colors.deepOrangeAccent),
home: Home(),
),
);
}}
Now this is the issue, within home.dart I retrieve the report data and build a list view, however the list view is called before the getReports method has finished and occasionally throws an error when referencing the var reports
home.dart:
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
var reports = Provider.of<List<Report>>(context);
FirestoreService _db = FirestoreService();
return Scaffold(
appBar: AppBar(
title: Text('Wax App'),
centerTitle: true,
actions: <Widget>[
IconButton(
icon: Icon(Icons.settings),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Settings()));
})
],
),
body: ListView.builder(
itemCount: reports.length,
itemBuilder: (context, index) {
Report report = reports[index];
return ListTile(
leading: Text(report.temp.toString()),
title: Text(report.wax),
subtitle: Text(report.line),
trailing: Text(formatDate(DateTime.parse(report.timeStamp), [h, ':', mm, ' ', am])));
}
) ,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
_db.addReport();
},
),
);
}}
For example one error in particular is thrown on this line:
itemCount: reports.length
reports being null at this point, so my question is how can I prevent the list view being built before the getReports methods has finished? what's the best way to handle such task?
Thanks
Try this:
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
var reports = Provider.of<List<Report>>(context);
FirestoreService _db = FirestoreService();
return Scaffold(
appBar: AppBar(
title: Text('Wax App'),
centerTitle: true,
actions: <Widget>[
IconButton(
icon: Icon(Icons.settings),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Settings()));
})
],
),
body: reports!=null ? (reports.length > 0 ? ListView.builder(
itemCount: reports.length,
itemBuilder: (context, index) {
Report report = reports[index];
return ListTile(
leading: Text(report.temp.toString()),
title: Text(report.wax),
subtitle: Text(report.line),
trailing: Text(formatDate(DateTime.parse(report.timeStamp), [h, ':', mm, ' ', am])));
}
): Center(child: Text("We have received no data"))) : Center(child: Text("We are fetching data.Please wait...")),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
_db.addReport();
},
),
);
}}