I wan't to add a loading screen t my flutter app when it's processing the data using the asyn but i am limited and don't know where to start from and this is my database.dart file which handles the firestore connections and configurations. Help me where can i add a function inside the DatabaseService which will be showing the loading screen and then after the async is done it displays the 'homepage' text.
Database.dart:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:kopala_dictionary/models/words.dart';
class DatabaseService {
//cloud databse colection for words
final CollectionReference wordsCollection =
Firestore.instance.collection('words');
Future insertData(String word, String english_translation,
String bemba_translation, String user_id, DateTime date_posted) async {
return await wordsCollection.document().setData({
'word': word,
'english_translation': english_translation,
'bemba_translation': bemba_translation,
'user_id': user_id,
'date_posted': date_posted
});
}
//words list from snappshots
List<Words> _wordsFromSnapShots(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return Words(
word: doc.data['word'],
englishTranslation: doc.data['english_translation'],
bembaTranslation: doc.data['bemba_translation'],
);
}).toList();
}
//Stream snapshots
Stream<List<Words>> get words {
// This forces an ordering on the documents in the collection
return wordsCollection.orderBy('word').snapshots().map(_wordsFromSnapShots);
}
}
My homepage
logged_home.dart:
import 'package:flutter/material.dart';
import 'package:kopala_dictionary/main/about.dart';
import 'package:kopala_dictionary/models/words.dart';
import 'package:kopala_dictionary/screens/author/profile_page.dart';
import 'package:kopala_dictionary/screens/home/words_list.dart';
import 'package:kopala_dictionary/screens/wrapper.dart';
import 'package:kopala_dictionary/services/auth.dart';
import 'package:kopala_dictionary/services/database.dart';
import 'package:kopala_dictionary/shared/app_bar.dart';
import 'package:provider/provider.dart';
class LoggedInUserHome extends StatelessWidget {
final AuthService _auth = AuthService();
#override
Widget build(BuildContext context) {
return StreamProvider<List<Words>>.value(
value: DatabaseService().words,
child: Scaffold(
backgroundColor: Colors.green[10],
appBar: LoggedBar(),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text(
'Kopalationary Menu',
style: TextStyle(color: Colors.white),
),
decoration: BoxDecoration(
color: Colors.green[800],
),
),
ListTile(
title: Text('Home'),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => Wrapper()));
},
),
ListTile(
title: Text(
'My profile',
),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => ProfilePage()));
},
),
ListTile(
title: Text('Logout'),
onTap: () async {
dynamic result = await _auth.logoutUser();
},
),
ListTile(
title: Text(
'About',
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => About()),
);
},
),
],
),
),
body: WordsList(),
),
);
}
}
You probably need a StreamBuilder for this. An example might look something like this. You pass a stream into the builder and handle whatever the state of the stream is inside of the builder by looking at the snapshot. If it has an data, you show the content for the data otherwise you show a loading screen or an error screen if there was an error while retrieving the data.
class MyScreen extends StatefulWidget {
#override
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
Stream<List<Words>> wordStream;
#override
void initState() {
super.initState();
wordStream = DatabaseService().words;
}
#override
Widget build(BuildContext context) {
return StreamBuilder<List<Words>>(
stream: wordStream,
builder: (context, snapshot) {
if (snapshot.hasError) {
return ErrorWidget();
} else if (snapshot.hasData) {
return ContentWidget(snapshot.data);
} else {
return LoadingWidget();
}
},
);
}
}
There are some other connectionStates on the snapshot, that you might want to handle, but this handles the standard cases.
Related
I'm developing an app using movies database API. I want to read a list of the movies and then choose one and display the details to user.
MovieDetailsBloc code:
import 'package:bloc/bloc.dart';
import 'package:bloc_concurrency/bloc_concurrency.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_recruitment_task/movie_details/models/movie_details.dart';
import 'package:flutter_recruitment_task/services/api_service.dart';
import 'package:stream_transform/stream_transform.dart';
part 'movie_details_event.dart';
part 'movie_details_state.dart';
const throttleDuration = Duration(milliseconds: 100);
EventTransformer<E> throttleDroppable<E>(Duration duration) {
return (events, mapper) {
return droppable<E>().call(events.throttle(duration), mapper);
};
}
class MovieDetailsBloc extends Bloc<MovieDetailsEvent, MovieDetailsState> {
MovieDetailsBloc({required this.apiService}) : super(MovieDetailsState()) {
on<MovieSelected>(
_onMovieSelected,
transformer: throttleDroppable(throttleDuration),
);
}
final ApiService apiService;
Future<void> _onMovieSelected(MovieSelected event, Emitter<MovieDetailsState> emit) async {
try{
final movie = await _fetchMovieDetails(event.query);
return emit(state.copyWith(
status: MovieDetailsStatus.success,
movie: movie,
));
} catch (_) {
emit(state.copyWith(status: MovieDetailsStatus.failure));
}
}
Future<MovieDetails> _fetchMovieDetails(String id) async {
final movie = apiService.fetchMovieDetails(id);
return movie;
}
String shouldIWatchIt() {
final movie = state.movie;
if(movie.revenue - movie.budget > 1000000 && DateTime.now().weekday == DateTime.sunday){
return 'Yes';
} else {
return 'No';
}
}
}
MovieListPage code:
import 'package:flutter/material.dart';
import 'package:flutter_recruitment_task/movie_details/bloc/movie_details_bloc.dart';
import 'package:flutter_recruitment_task/movies/bloc/movies_bloc.dart';
import 'package:flutter_recruitment_task/movies/models/movie.dart';
import 'package:flutter_recruitment_task/pages/movie_details/movie_details_page.dart';
import 'package:flutter_recruitment_task/pages/movie_list/movie_card.dart';
import 'package:flutter_recruitment_task/pages/movie_list/search_box.dart';
import 'package:flutter_recruitment_task/services/api_service.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MovieListPage extends StatefulWidget {
#override
_MovieListPage createState() => _MovieListPage();
}
class _MovieListPage extends State<MovieListPage> {
final ApiService apiService = ApiService();
late final MoviesBloc moviesBloc;
late final MovieDetailsBloc movieDetailsBloc;
void _onSearchBoxSubmitted(String text) {
moviesBloc.add(MoviesFetched(query: text));
}
#override
void initState() {
moviesBloc = MoviesBloc(apiService: apiService);
movieDetailsBloc = MovieDetailsBloc(apiService: apiService);
super.initState();
}
#override
void dispose() {
movieDetailsBloc.close();
moviesBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) => BlocProvider(
create: (_) => movieDetailsBloc,
child: Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.movie_creation_outlined),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => MovieDetailsPage(
movieDetailsBloc: movieDetailsBloc,
)
)
);
},
),
],
title: Text('Movie Browser'),
),
body: BlocProvider(
create: (_) => moviesBloc,
child: Column(
children: <Widget>[
SearchBox(onSubmitted: _onSearchBoxSubmitted),
Expanded(child: _buildContent()),
],
),
),
),
);
Widget _buildContent() => BlocBuilder<MoviesBloc, MoviesState>(
buildWhen: (previous, current) => previous != current,
builder: (context, state) {
switch (state.status) {
case MoviesStatus.failure:
return const Center(child: Text('failed to fetch movies'));
case MoviesStatus.success:
if (state.movies.isEmpty) {
return const Center(child: Text('no movies'));
}
return _buildMoviesList(state.movies);
case MoviesStatus.initial:
return const Center(child: CircularProgressIndicator());
}
});
Widget _buildMoviesList(List<Movie> movies) => ListView.separated(
separatorBuilder: (context, index) => Container(
height: 1.0,
color: Colors.grey.shade300,
),
itemBuilder: (context, index) => BlocBuilder<MovieDetailsBloc, MovieDetailsState>(
buildWhen: (previous, current) => previous != current,
builder: (context, state) {
return MovieCard(
title: movies[index].title,
rating: '${(movies[index].voteAverage * 10).toInt()}%',
color: state.movie.title == movies[index].title ?
Colors.amberAccent : Colors.white,
onTap: () {
movieDetailsBloc.add(MovieSelected(query: movies[index].id.toString()));
},
);
}
),
itemCount: movies.length,
);
}
MovieDetailsPage code:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_recruitment_task/movie_details/bloc/movie_details_bloc.dart';
import 'package:flutter_recruitment_task/movie_details/models/movie_detail_placeholder.dart';
class MovieDetailsPage extends StatefulWidget {
final MovieDetailsBloc? movieDetailsBloc;
const MovieDetailsPage({super.key, this.movieDetailsBloc});
#override
_MovieDetailsPageState createState() => _MovieDetailsPageState();
}
class _MovieDetailsPageState extends State<MovieDetailsPage> {
var _details = [];
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) => BlocProvider(
create: (_) => widget.movieDetailsBloc!,
child: BlocBuilder<MovieDetailsBloc, MovieDetailsState>(
buildWhen: (previous, current) => previous != current,
builder: (context, state) {
_details = [
MovieDetailPlaceholder(title: 'Budget', content: '\$ ${state.movie.budget}'),
MovieDetailPlaceholder(title: 'Revenue', content: '\$ ${state.movie.revenue}'),
MovieDetailPlaceholder(title: 'Should I watch it today?', content: widget.movieDetailsBloc!.shouldIWatchIt()),
];
return Scaffold(
appBar: AppBar(
title: Text(state.movie.title),
),
body: ListView.separated(
separatorBuilder: (context, index) => Container(
height: 1.0,
color: Colors.grey.shade300,
),
itemBuilder: (context, index) => Container(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
_details[index].title,
style: Theme.of(context).textTheme.headline5,
),
SizedBox(height: 8.0),
Text(
_details[index].content,
style: Theme.of(context).textTheme.subtitle1,
),
],
),
),
itemCount: _details.length,
),
);
}
),
);
}
I'm passing an instance of BLoC from MovieListPage to MovieDetailsPage because it holds the details of the movie to display. It works fine for one time, however when I go back to the MovieListPage I'm not able to choose a new movie to display the details. How I can make an instance of BLoC persist through navigation?
Create your bloc's intance after your bloc ends like
class MovieDetailsBloc extends Bloc<MovieDetailsEvent, MovieDetailsState> {
MovieDetailsBloc({required this.apiService}) : super(MovieDetailsState()) {
on<MovieSelected>(
_onMovieSelected,
transformer: throttleDroppable(throttleDuration),
);
}
final ApiService apiService;
Future<void> _onMovieSelected(MovieSelected event, Emitter<MovieDetailsState> emit) async {
try{
final movie = await _fetchMovieDetails(event.query);
return emit(state.copyWith(
status: MovieDetailsStatus.success,
movie: movie,
));
} catch (_) {
emit(state.copyWith(status: MovieDetailsStatus.failure));
}
}
Future<MovieDetails> _fetchMovieDetails(String id) async {
final movie = apiService.fetchMovieDetails(id);
return movie;
}
String shouldIWatchIt() {
final movie = state.movie;
if(movie.revenue - movie.budget > 1000000 && DateTime.now().weekday == DateTime.sunday){
return 'Yes';
} else {
return 'No';
}
}
}
MovieDetailsBloc movieDetailsBloc = MovieDetailsBloc();
and you can access it anywhere in your code with movieDetailsBloc.add or whatever you want.
ListTile(
leading: Icon(Icons.person),
title: const Text('Profilim'),
onTap: () async {
print("aaaaaaa");
Navigator.push(
context,
MaterialPageRoute(builder: (context) => profile(gelenid : widget.gelenid)),
);
},
),
When I press this button im gettin nosuchmethoderror and I have this code in profile.dart
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:personal_planner/update.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class profile extends StatefulWidget {
const profile({this.gelenid});
final gelenid;
#override
State<profile> createState() => _profileState();
}
class _profileState extends State<profile> {
var satir;
dbGoster() async {
Directory klasor = await getApplicationDocumentsDirectory();
String veritabyolu = join(klasor.path, "personal.sqlite");
Database db = await openDatabase(veritabyolu);
if (await databaseExists(veritabyolu)){
print("Var");
List<Map<String,dynamic>> maps=await db.rawQuery("SELECT * FROM personals WHERE id = ?" ,[widget.gelenid.toString()]);
List.generate(maps.length, (index) {
satir=maps[index];
});} else {
print("Veri tabanı yok");
};
}
#override
Widget build(BuildContext context) {
dbGoster();
return Scaffold(
appBar: AppBar(
title: Text("Profilim"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("ID=>"+satir["id"].toString()),
Text("TC Kimlik=>"+satir["tc"].toString()),
Text("İsim=>"+satir["isim"]),
Text("Soyisim=>"+satir["soyisim"]),
Text("Sifre=>"+satir["sifre"]),
Text("Medeni=>"+satir["medeni"].toString() ),
Text("İlgi Alanları=>"+satir["ilgialan"].toString()),
Text("Ehliyet=>"+satir["ehliyet"].toString()),
ElevatedButton(onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => update(gelenid : widget.gelenid)),
);
}, child: Text("Güncellemek için bas!")),
],
),
),
);
}
}
I tried every possible what I know and found but I couldn't fix.
I think the sql query must be do first but I tried initstate etc.
I tried if else statement in children for text widget but it couldn't help
Except this-> When I press button I got nosuchmethoderror but if I do hotreload page comes on my phone
I am trying to add a search function in my flutter app, the search bar is showing and there's not errors but its not working and it doesn't return any results.
the data list is from an API that I already called using the rest API
// ignore_for_file: use_key_in_widget_constructors, avoid_print, avoid_unnecessary_containers, curly_braces_in_flow_control_structures, prefer_const_constructors, non_constant_identifier_names, unnecessary_new, avoid_function_literals_in_foreach_calls, unused_import, avoid_types_as_parameter_names, unused_label
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:myapp2/Service_Request/SR.dart';
import 'package:myapp2/main.dart';
import 'package:myapp2/Service_Request/second.dart';
import '../Classes/demandes.dart';
import 'SR_details.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DataFromAPI(),
);
}
}
class DataFromAPI extends StatefulWidget {
#override
_DataFromAPIState createState() => _DataFromAPIState();
}
List<Attributes> _MyAllData = [];
var _srAttributes = [];
class _DataFromAPIState extends State<DataFromAPI> {
#override
void initState() {
loadData().then((value) {
setState(() {
_srAttributes.addAll(value);
});
});
super.initState();
}
Future<List<Sr>> loadData() async {
try {
var response = await http.get(Uri.parse(
'http://192.168.1.30:9080/maxrest/rest/mbo/sr/?_lid=azizl&_lpwd=max12345m&_format=json'));
if (response.statusCode == 200) {
final jsonBody = json.decode(response.body);
Demandes data = Demandes.fromJson(jsonBody);
final srAttributes = data.srMboSet.sr;
return srAttributes;
}
} catch (e) {
throw Exception(e.toString());
}
throw Exception("");
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: new Scaffold(
appBar: AppBar(
title: Text('Liste des Demandes'),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.push(
context, MaterialPageRoute(builder: (context) => SR()))),
),
body: FutureBuilder<List<Sr>?>(
future: loadData(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
} else {
return new ListView.builder(
itemCount: snapshot.data?.length,
itemBuilder: ((_, index) {
return index == 0
? _searchbar()
: new ListTile(
title: new Card(
margin: new EdgeInsets.symmetric(
vertical: 2.0, horizontal: 8.0),
elevation: 10,
child: new ListTile(
title: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(padding: new EdgeInsets.all(2.0)),
new Text(
'Ticket ID : ${snapshot.data![index].attributes.ticketid.content}'),
new Text(
'status : ${snapshot.data![index].attributes.status.content}'),
new Text(
'description : ${snapshot.data![index].attributes.description?.content}'),
new Text(
'Reported by : ${snapshot.data![index].attributes.reportedby.content}'),
new Text(
'Reoprt date : ${snapshot.data![index].attributes.statusdate.content}'),
],
),
trailing: Icon(Icons.arrow_forward_ios_rounded),
),
),
onTap: () {
Navigator.of(context)
.push(
new MaterialPageRoute(
builder: (BuildContext context) =>
new SrDetailsScreen(
sr: snapshot.data![index]),
),
)
.then((data) {});
});
}),
);
}
},
),
),
);
}
_searchbar() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: InputDecoration(hintText: "Search ..."),
onChanged: (text) {
text = text.toLowerCase();
setState(() {
_srAttributes = _MyAllData.where((srAttributes) {
var idticket = srAttributes.description!.content.toLowerCase();
return idticket.contains(text);
}).toList();
});
},
),
);
}
}
FutureBuilder loads values of current future. You are assigning a function result to FutureBuilder so its value always changes dynamically.
Create variable to keep Future's value.
Future<List<Sr>>? dataToLoad;
Whenever you want to load data from server ( for example, on text changed ):
setState((){
dataToLoad = loadData();
});
And use it in FutureBuilder:
FutureBuilder<List<Sr>?>(
future: dataToLoad,
I am trying to switch the drawer tab, according to the value stored in shared preferences using the following code.
code works fine when memoizer is not used but future builder runs forever.
If I use memorizer future builder still runs at least two times (not forever), but get and set functions doesn't work and new values are not updated and are not notified to the widgets.
I need some way to stop running future builder forever and notify users as well accordingly by triggering get and set functions present in it
Notifier class
class SwitchAppProvider extends ChangeNotifier {
switchApp(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('key', value);
notifyListeners();
}
Future<bool?> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('key');
return value;
}
}
Drawer
Widget _buildDrawer() {
return ChangeNotifierProvider<SwitchAppProvider>(
create: (context) => SwitchAppProvider(),
child: Consumer<SwitchAppProvider>(
builder: (context, provider, _) {
return Container(
width: 260,
child: Drawer(
child: Material(
color: Color.fromRGBO(62, 180, 137, 1),
child: ListView(
children: <Widget>[
Container(
padding: AppLandingView.padding,
child: Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context,
AsyncSnapshot<dynamic> snapshot) {
if (snapshot.data == true) {
return _buildMenuItem(
text: 'widget1',
icon: Icons.add_business,
onTap: () {
provider.switchApp(false);
},
);
} else {
return _buildMenuItem(
text: 'widget2',
icon: Icons.add_business,
onTap: () {
provider.switchApp(true);
},
);
}
},
),
],
),
),
],
),
),
),
);
},
),
);
}
Scaffold
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: _buildDrawer(),
);
}
Update
I analysed further, problem lies in provider.getValue(), if i use notifyListeners() before returning the value future builder runs forever
I removed it and the future builder doesn't run forever, but it doesn't update other widgets.
Scenario is
widget 1
contains a drawer
has a button to switch app
on tap value is set using shared preferences (setValue() function) and listeners are notified
in widget 1 notifier is working well and changing the drawer button option when setValue() is called on tap.
everything resolved in widget 1, as its calling setValue() hence notifyListeners() is triggered and widget1 is rerendered
widget 2
only gets value from shared preferences(getValue() function). getValue function cant use notifyListeners(), if used futurebuilder is running forever
widget 2 don't set any value so it doesn't use setValue() hence it's not getting notified
how I can notify widget 2, when on tap setValue() is triggered in widget 1
i.e widget1 sets the app using setValue() function
widget2 gets value from getValue() function and get notified
Update 2
class SwitchAppProvider with ChangeNotifier {
dynamic _myValue;
dynamic get myValue => _myValue;
set myValue(dynamic newValue) {
_myValue = newValue;
notifyListeners();
}
setValue(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('key', value);
notifyListeners();
}
SwitchAppProvider(){
getValue();
}
Future<void> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
myValue = prefs.getBool('key');
}
}
widget 2
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: SwitchAppProvider(),
child: Consumer<SwitchAppProvider>(
builder: (BuildContext context, SwitchAppProvider provider, _) {
if (provider.myValue == true) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Container(
child: Text('provider.myValue'));
}
})
);
}
}
_buildMenuItem
// helper widget to build item of drawer
Widget _buildMenuItem({
required String text,
required IconData icon,
required GestureTapCallback onTap,
}) {
final color = Colors.white;
final hoverColor = Colors.white;
return ListTile(
leading: Icon(icon, color: color),
title: Text(text, style: TextStyle(color: color, fontSize: 18)),
hoverColor: hoverColor,
onTap: onTap,
);
}
"If I use memorizer future builder still runs at least two times (not forever), but get and set functions doesn't work and new values are not updated and are not notified to the widgets."
That is the expected behaviour:
An AsyncMemoizer is used when some function may be run multiple times in order to get its result, but it only actually needs to be run once for its effect.
so prefs.setBool('key', value); is executed only the first time.
You definitely do not want to use it.
If you edit your code to remove the AsyncMemoizer, we can try to help you further.
Edit after Update
You are right, the getValue() function should not notify listeners, if it does that, then the listeners will rebuild and ask for the value again, which will notify listeners, which will rebuild and ask for the value again, which... (you get the point).
There is something wrong in your reasoning. widget1 and widget2 are not notified, the Consumer is notified. Which will rebuild everything. The code is quite complicated and it could be simplified a lot by removing unneeded widgets.
I will suggest you to
await prefs.setBool('isWhatsappBusiness', value); before notifying listeners.
have a look at this answer for a similar problem.
Edit 3
I do not know what you are doing wrong, but this works:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class SwitchAppProvider extends ChangeNotifier {
switchApp(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('key', value);
notifyListeners();
}
Future<bool?> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('key');
return value;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
drawer: _buildDrawer(),
),
);
}
Widget _buildDrawer() {
return ChangeNotifierProvider<SwitchAppProvider>(
create: (context) => SwitchAppProvider(),
child: Consumer<SwitchAppProvider>(
builder: (context, provider, _) {
return SizedBox(
width: 260,
child: Drawer(
child: Material(
color: const Color.fromRGBO(62, 180, 137, 1),
child: ListView(
children: <Widget>[
Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context,
AsyncSnapshot<dynamic> snapshot) {
print('Am I building?');
if (snapshot.data == true) {
return ListTile(
tileColor: Colors.red[200],
title: const Text('widget1'),
leading: const Icon(Icons.flutter_dash),
onTap: () {
provider.switchApp(false);
},
);
} else {
return ListTile(
tileColor: Colors.green[200],
title: const Text('widget2'),
leading: const Icon(Icons.ac_unit),
onTap: () {
provider.switchApp(true);
},
);
}
},
),
],
),
],
),
),
),
);
},
),
);
}
}
If you still cannot get it working, then the problem is somewhere else.
Edit 4
First, I suggest you to be more clear in future questions. Write all the code that is needed immediately and remove widgets that are not needed. Avoid confusion given by naming different things in the same way.
The second widget does not update because it is listening to a different notifier.
When you do
return ChangeNotifierProvider.value(
value: SwitchAppProvider(),
in Widget2 you are creating a new provider object, you are not listening to changes in the provider you created in the Drawer.
You need to move the ChangeNotifierProvider.value widget higher in the widget tree, and use the same one:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class SwitchAppProvider extends ChangeNotifier {
switchApp(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('key', value);
notifyListeners();
}
Future<bool?> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('key');
return value;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider<SwitchAppProvider>(
create: (context) => SwitchAppProvider(),
child: Scaffold(
appBar: AppBar(),
drawer: _buildDrawer(),
body: const Widget2(),
),
),
);
}
Widget _buildDrawer() {
return Consumer<SwitchAppProvider>(builder: (context, provider, _) {
return SizedBox(
width: 260,
child: Drawer(
child: Material(
color: const Color.fromRGBO(62, 180, 137, 1),
child: ListView(
children: <Widget>[
Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context,
AsyncSnapshot<dynamic> snapshot) {
print('Am I building?');
if (snapshot.data == true) {
return ListTile(
tileColor: Colors.red[200],
title: const Text('widget1'),
leading: const Icon(Icons.flutter_dash),
onTap: () {
provider.switchApp(false);
},
);
} else {
return ListTile(
tileColor: Colors.green[200],
title: const Text('widget2'),
leading: const Icon(Icons.ac_unit),
onTap: () {
provider.switchApp(true);
},
);
}
},
),
],
),
],
),
),
),
);
});
}
}
class Widget2 extends StatelessWidget {
const Widget2({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<SwitchAppProvider>(
builder: (BuildContext context, SwitchAppProvider provider, _) {
return FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
print('Am I building even more ?');
if (snapshot.data == true) {
return const Center(
child: CircularProgressIndicator(),
);
} else {
return const Text('provider.myValue');
}
},
);
},
);
}
}
I need to grab a list of all messages in the database where the level is less than or equal to the user's current level. To do this I am attempting to use a nested streambuilder. First one pulls the user info, then I use the user level to make the second query. My problem is I get an error.
Null check operator used on null value
I do not understand this error. I checked firestore to see that there is data in the collection, and I also made sure to put the proper rules in place. I've tried several variations of this code and this is the only one so far that at least doesn't give me an error in the emulator. My emulator shows the appbar and the body is blank.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import '../screens/welcome_screen.dart';
class MessagesScreen extends StatefulWidget {
static const String id = 'messages_screen';
#override
_MessagesScreenState createState() => _MessagesScreenState();
}
class _MessagesScreenState extends State<MessagesScreen> {
final _firestore = FirebaseFirestore.instance;
final _auth = FirebaseAuth.instance;
User loggedInUser;
int _userCurrentLevel;
#override
void initState() {
super.initState();
getCurrentUser();
}
void getCurrentUser() {
try {
final user = _auth.currentUser;
if (user != null) {
loggedInUser = user;
}
} catch (e) {
print(e);
}
}
getMessages(AsyncSnapshot<QuerySnapshot> snapshot2) {
return snapshot2.data.docs
.map((doc) => new ListTile(title: Text(doc['from']), subtitle: Text(doc['text'])))
.toList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Darker Slate'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.chat),
tooltip: 'Messages',
onPressed: () {},
),
IconButton(
icon: const Icon(Icons.exit_to_app),
tooltip: 'Log Out',
onPressed: () {
_auth.signOut();
Navigator.pushNamed(context, WelcomeScreen.id);
},
),
],
),
body: StreamBuilder<DocumentSnapshot>(
stream: _firestore.collection('users')
.doc(_auth.currentUser.uid)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.red[900],
),
);
}
_userCurrentLevel = snapshot.data['userlevel'];
return StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('messages')
.where('level', isLessThanOrEqualTo: _userCurrentLevel).snapshots(),
builder: (context, snapshot2) {
if (!snapshot2.hasData) {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.red[900],
),
);
}
return Column(
children: [
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshot2.data.docs.length,
itemBuilder: (_, index) {
return new ListView(children: getMessages(snapshot2));
}),
],
);
}
);
}),
);
}
}