import 'package:flutter/material.dart';
import 'package:futureprovider/Employee.dart';
import 'package:futureprovider/EmployeeService.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp(
));
class MyApp extends StatelessWidget {
final PostService fur = PostService();
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
FutureProvider(create:(context)=> fur.getEmployees(),
catchError: (context,e){
print(e.toString());
},
//initialData: [Post(id:1,name: 1,title: '1',body: '1')],
)
],
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Posts Dashboard")),
body:TilesOfPost(),
),
),
);
}
}
class TilesOfPost extends StatelessWidget {
#override
Widget build(BuildContext context) {
List<Post> posts = Provider.of<List<Post>>(context);
print("Length:"+posts.length.toString());
return (posts==null)? Center(child:CircularProgressIndicator()):ListView.builder(
itemCount: posts.length,
itemBuilder: (context,index){
return ListTile(
title: Text(posts[index].id.toString()),
subtitle: Text(posts[index].name.toString())
);
}
);
}
}
In the following code,
I have Future Provider which provides some future values and when i execute the code,
initially when the List is null it should show Circular Progress Widget,
but it gives an exception for the ListView being build with length null.
How do i show Circular Progress Inidcator when the Future value is still not received
Your print statement is the actual problem. when data is null then also it check for length, so i throws error because of that you are also not able to see CircularProgressIndicator().
Related
I've been working with Flutter recently, and I saw that there was many ways to deal with state management.
Following the recommendations there, I've been using Provider to deal with the state of my app.
I can update a part of my state from one of the widgets in my UI. To do that, I can call a method of the provider that's above the current widget in the context. No problems with this.
But I want the update of my state to be made from an overlay.
The issue is: When I'm inserting an OverlayEntry with Overlay.of(context)?.insert(), it inserts the overlayEntry to the closest Overlay, which is in general the root of the app, which is above the ChangeProvider. As a result, I get an exception saying I can't find the Provider from the OverlayEntry.
Here is a replication code I've been writting:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ChangeNotifierProvider(
create: (context) => NumberModel(), // All widgets that will be lower in the widget tree will have access to NumberModel
child: NumberDisplayer()
),
);
}
}
// Simple ChangeNotifier. We have a number that we can increment.
class NumberModel extends ChangeNotifier {
int _number = 10;
int get number => _number;
void add_one() {
_number = number + 1;
notifyListeners();
}
}
// This class displays a number, and a button.
class NumberDisplayer extends StatelessWidget {
NumberDisplayer({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var overlayEntry = OverlayEntry(builder: (context) =>
Positioned(
top: 100,
left: 50,
child: FloatingActionButton(onPressed: (){
// Throws "Error: Could not find the correct Provider<NumberModel> above this _OverlayEntryWidget Widget"
Provider.of<NumberModel>(context, listen: false).add_one();
})));
return Consumer<NumberModel>(
builder: (context, numberModel, child) {
return Column(
children: [
Text('Number: ${numberModel.number}'),
FloatingActionButton(onPressed: () {
Overlay.of(context)?.insert(overlayEntry);
})
],
);
},
);
}
}
I would like to find a way to update the information in my provider from the overlay, but I'm not sure how to approach this problem.
Thanks for your help everyone !
this is my first question and I hope it's readable.
I've created a sample flutter project where I've illustrated the problem specified in my question.
If the code runs on Android the AndroidAppVersion-Object creates a Material App that contains an AndroidHomeScreen-Object with a red Scaffold and a Button with the title "Android". When the user presses the button the AnotherPageView-Object appears that contains an orange Scaffold.
The AnotherPageView-Object is just a sample page.
If the Platform isn't Android an (IOS-)Cupertino-App will be created by the IOSAppVersion-Object that contains an IOSHomeScreen with a green Scaffold with a Button with the title "IOS". Again the user presses the button to create an AnotherPageView-Object.
So here is the Problem:
When I use
Navigator.pushNamed(context, AnotherPageView.anotherPage);
The code doesn't work and it gives me the error I've added down below.
However, it works if I use
Navigator.push(context,
CupertinoPageRoute(builder: (context) => AnotherPageView()));
but I'd like to know why it doesn't work with the first one.
The code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:io';
void main() {
runApp(Platform.isAndroid ? IOSAppVersion() : AndroidAppVersion());
}
class AndroidAppVersion extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
AnotherPageView.anotherPage: (context) {
return AnotherPageView();
}
},
home: AndroidHomeScreen(),
);
}
}
class AndroidHomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.red,
body: MaterialButton(
onPressed: () {
Navigator.pushNamed(context, AnotherPageView.anotherPage);
},
child: Center(
child: Text('Android'),
),
),
);
}
}
class IOSAppVersion extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoApp(
home: IOSHomeScreen(),
);
}
}
class IOSHomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green,
body: CupertinoButton(
child: Center(child: Text('Hallo IOS')),
onPressed: () {
Navigator.pushNamed(context, AnotherPageView.anotherPage);
},
),
);
}
}
class AnotherPageView extends StatelessWidget {
static String anotherPage = "anotherPage";
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.orange,
);
}
}
Here's the error that gets shown:
The following assertion was thrown while handling a gesture:
Could not find a generator for route RouteSettings("anotherPage", null) in the _WidgetsAppState.
Generators for routes are searched for in the following order:
For the "/" route, the "home" property, if non-null, is used.
Otherwise, the "routes" table is used, if it has an entry for the route.
Otherwise, onGenerateRoute is called. It should return a non-null value for any valid route not handled by "home" and "routes".
Finally if all else fails onUnknownRoute is called.
Unfortunately, onUnknownRoute was not set.
...
This
return CupertinoApp(
home: IOSHomeScreen(),
);
doesn't know any routes, you need to provide your routes to both materialapp and cupertinoapp, just do
return CupertinoApp(
routes: {
AnotherPageView.anotherPage: (context) {
return AnotherPageView();
}
},
home: IOSHomeScreen(),
);
import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
//Using Bloc
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.darkThemeEnabled,
initialData: false,
builder: (context, snapshot) => MaterialApp(
theme: snapshot.data ? ThemeData.dark() : ThemeData.light(),
home: HomePage(snapshot.data)),
);
}
}
class HomePage extends StatelessWidget {
final bool darkThemeEnabled;
HomePage(this.darkThemeEnabled);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Dynamic Theme"),
),
body: Center(
child: Text("Hello World"),
),
drawer: Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Dark Theme"),
trailing: Switch(
value: darkThemeEnabled,
onChanged: bloc.changeTheme,
),
)
],
),
),
);
}
}
class Bloc {
final _themeController = StreamController<bool>();
get changeTheme => _themeController.sink.add;
get darkThemeEnabled => _themeController.stream;
}
final bloc = Bloc();
1.A warning says to Close instances of dart.core.sink
2.Why dart.core.sink is used in flutter?
3.How can I solve this error
4.Its error documentation redirects me to this website link
5.I don't know how to use these methods in flutter please guide me
dart.core.sink is an interface that is implemented by Stream.
The warning is showing, because the dart compiler wants you to .close() your instance of a Stream. In this case that is your final _themeController = StreamController<bool>().
If you want to fix the warning, add
void dispose() {
_themeController.close();
}
to your Bloc class.
Just adding the method is not doing much, since it's not called. So you should change your main() method to call bloc.dispose() after runApp(MyApp()).
That error occur when missing close StreamController.
Simple way to fix:
Create abstract class:
abstract class Bloc {
void dispose();
}
Your bloc class implements Bloc, now you can close StreamController in dispose:
class ColorBloc implements Bloc {
// streams of Color
StreamController streamListController = StreamController<Color>.broadcast();
// sink
Sink get colorSink => streamListController.sink;
// stream
Stream<Color> get colorStream => streamListController.stream;
// function to change the color
changeColor() {
colorSink.add(getRandomColor());
}
// Random Colour generator
Color getRandomColor() {
Random _random = Random();
return Color.fromARGB(
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
);
}
// close Stream
#override
void dispose() {
streamListController.close();
}
}
I have two streams:
Stream<FirebaseUser> FirebaseAuth.instance.onAuthStateChanged
Stream<User> userService.streamUser(String uid)
My userService requires the uid of the authenticated FirebaseUser as a parameter.
Since I will probably need to access the streamUser() stream in multiple parts of my app, I would like it to be a provider at the root of my project.
This is what my main.dart looks like:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
var auth = FirebaseAuth.instance;
var userService = new UserService();
return MultiProvider(
providers: [
Provider<UserService>.value(
value: userService,
),
],
child: MaterialApp(
home: StreamBuilder<FirebaseUser>(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (!snapshot.hasData) return LoginPage();
return StreamProvider<User>.value(
value: userService.streamUser(snapshot.data.uid),
child: HomePage(),
);
}),
),
);
}
}
The issue is that when I navigate to a different page, everything below the MaterialApp is changed out and I lose the context with the StreamProvider.
Is there a way to add the StreamProvider to the MultiProvider providers-list?
Because when I try, I also have to create another onAuthStateChanged stream for the FirebaseUser and I don't know how to combine them into one Provider.
So this seems to work fine:
StreamProvider<User>.value(
value: auth.onAuthStateChanged.transform(
FlatMapStreamTransformer<FirebaseUser, User>(
(firebaseUser) => userService.streamUser(firebaseUser.uid),
),
),
),
If anybody has doubts about this in certain edge cases, please let me know.
Thanks to pskink for the hint about flatMap.
Maybe you can try this approach:
main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<FirebaseUser>(
builder: (_) => FirebaseUser(),
),
],
child: AuthWidgetBuilder(builder: (context, userSnapshot) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.indigo),
home: AuthWidget(userSnapshot: userSnapshot),
);
}),
);
}
}
AuthWidgetBuilder.dart
Used to create user-dependant objects that need to be accessible by
all widgets. This widget should live above the [MaterialApp]. See
[AuthWidget], a descendant widget that consumes the snapshot generated
by this builder.
class AuthWidgetBuilder extends StatelessWidget {
const AuthWidgetBuilder({Key key, #required this.builder}) : super(key: key);
final Widget Function(BuildContext, AsyncSnapshot<User>) builder;
#override
Widget build(BuildContext context) {
final authService =
Provider.of<FirebaseUser>(context, listen: false);
return StreamBuilder<User>(
stream: authService.onAuthStateChanged,
builder: (context, snapshot) {
final User user = snapshot.data;
if (user != null) {
return MultiProvider(
providers: [
Provider<User>.value(value: user),
Provider<UserService>(
builder: (_) => UserService(uid: user.uid),
),
],
child: builder(context, snapshot),
);
}
return builder(context, snapshot);
},
);
}
}
AuthWidget.dart
Builds the signed-in or non signed-in UI, depending on the user
snapshot. This widget should be below the [MaterialApp]. An
[AuthWidgetBuilder] ancestor is required for this widget to work.
class AuthWidget extends StatelessWidget {
const AuthWidget({Key key, #required this.userSnapshot}) : super(key: key);
final AsyncSnapshot<User> userSnapshot;
#override
Widget build(BuildContext context) {
if (userSnapshot.connectionState == ConnectionState.active) {
return userSnapshot.hasData ? HomePage() : SignInPage();
}
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
This is originally from the tutorial of advance provider from Andrea Bizotto.
But I tailored some the code according to your your code above.
Hope this works, good luck!
Reference:
https://www.youtube.com/watch?v=B0QX2woHxaU&list=PLNnAcB93JKV-IarNvMKJv85nmr5nyZis8&index=5
I made a function to fetch data from json file and I show that data to one page when ever my fetch function run it show an erorr for the time till Json fetch that is 3 to 4 second after that data fetch and show succesfully but that error show on screen is very awkward.
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(News1());
class News1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter",
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List data = [];
#override
void initState() {
fetchData();
super.initState();
}
void fetchData() async {
final response = await http.get('jsonfilelinkhere');
if (response.statusCode == 200) {
setState(() {
data = json.decode(response.body);
});
}
}
#override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations(
[DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
return Scaffold(
body: Padding(
padding: EdgeInsets.only(left: 50 ,right: 50),
child:ListView(
children: <Widget>[
Center(
child: Text(data[3]['Head']),
),
Center(
child: Text(data[0]['Description']),
),
Image.network(data[0]['ImgUrl']),
],
),
)
);
}
}
hope you got your answer. In case you can make a check that while your array is equal to null show CircularProgressIndicator(), else show data. If you are unable to do so I can share the code for you.
your fetchData() function is asynchronous, so your app tap the back of your function saying "hi, fetchData() start to work!!" but your app goes on minding its own job.
And you gave this job for it:
child: Text(data[3]['Head']),
so your app will hit this line of code while your data variable still is an empty list.
You have to prepare it for this situation. You can prepare the default value of the data or you can check if it's empty in the Widgets that depends on it.
You encountered that error as you displayed that data before it could actually load.
Use FutureBuilder to solve your issue.
Example code:
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(News1());
class News1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter",
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List data = [];
#override
void initState() {
fetchData();
super.initState();
}
Future<Map<String, dynamic>> fetchData() async {
final response = await http.get('jsonfilelinkhere');
if (response.statusCode == 200) {
setState(() {
return json.decode(response.body);
});
}
}
#override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations(
[DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
return Scaffold(
body: Padding(
padding: EdgeInsets.only(left: 50 ,right: 50),
child:FutureBuilder<Map<String, dynamic>>(
future: fetchData, // async work
builder: (context,snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting: return new Center(child: Text('Loading....'));
default:
if (snapshot.hasError)
return Text("Error!");
else{
data = snapshot.data;
return ListView(
children: <Widget>[
Center(
child: Text(data[3]['Head']),
),
Center(
child: Text(data[0]['Description']),
),
Image.network(data[0]['ImgUrl']),
],
)}
}
},
),
)
);
}
}