Related
I want to get all docs from a collection in one shot without knowing the docs id's since they are random. enter image description hereI am creating an Admin panel so for that I want to show all sensor data without a specific UID how can I do this if I pass any user id and then show only this user info I want to get all user info in my admin panel.
import 'package:flutter/material.dart';
import 'package:health_monitoring_app/auth/auth_service.dart';
import 'package:health_monitoring_app/database/database_helper.dart';
import 'package:health_monitoring_app/model/sensor_data_model.dart';
import 'package:health_monitoring_app/provider/sensor_data_provider.dart';
import 'package:health_monitoring_app/view/user_data_list.dart';
import 'package:health_monitoring_app/view/welcome_screen.dart';
import 'package:provider/provider.dart';
class DoctorDashboard extends StatefulWidget {
const DoctorDashboard({Key? key}) : super(key: key);
static const String routeNames = '/DoctorDashboard';
#override
State<DoctorDashboard> createState() => _DoctorDashboardState();
}
class _DoctorDashboardState extends State<DoctorDashboard> {
final user = AuthService.currentUser;
late SensorDataProvider _sensorDataProvider;
List<Object> _dataList = [];
#override
void didChangeDependencies() {
_sensorDataProvider = Provider.of<SensorDataProvider>(context);
super.didChangeDependencies();
getUsersDataList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue.shade900,
),
body: ListView.builder(
itemCount: _dataList.length,
itemBuilder: (context, index) {
return UserDataList(_dataList[index] as SensorDataModel);
},
),
drawer: Drawer(
child: ListView(
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue.shade900,
// image: const DecorationImage(
// image: AssetImage("assets/heart-beat.png"),
// )
),
child: Center(
child: Text(
'${user?.email}',
style: const TextStyle(color: Colors.white),
),
)),
ListTile(
onTap: _doctorSignOut,
leading: const Icon(Icons.logout),
title: const Text('Sign Out'),
)
],
),
),
);
}
void _doctorSignOut() {
AuthService.signOut().then((_) {
Navigator.pushReplacementNamed(context, WelcomeScreen.routeNames);
});
}
Future getUsersDataList() async {
final uid = AuthService.currentUser?.uid;
var data = await DatabaseHelper.db
.collection('sensorData')
.doc()
.collection('userData')
.orderBy('timestamp', descending: true)
.get();
setState(() {
_dataList =
List.from(data.docs.map((doc) => SensorDataModel.fromSnapshot(doc)));
});
}
}
I have a list of cards and each card has a long press function which when clicked, pops up an alert dialog. I would like the card to change color based on the option chosen in the alert dialog. My alert dialog has 3 options:
Completed (Card should change to color green),
In Progress ( Color orange),
Cancel (Color grey).
At first, when the screen loads, it should show list of cards each painted the color based on the value saved in the database. Then, when the user long presses a card and chooses an option from the alert dialog, the card's color should change based on the chosen option. Only that particular card's color should change.
I have read somewhere that this might be achievable using valuechangenotifier. So here's what I did so far:
First I created my changenotifier class like below:
import 'package:flutter/material.dart';
class ColorChanger with ChangeNotifier{
Color _color = Colors.white;
ColorChanger(this._color);
getColor() => _color;
setTheme (Color color) {
_color = color;
notifyListeners();
}
}
Then I used it in my dart class. However, the color does not seem to change. What am I missing here?
class OrderItem extends StatefulWidget {
final ord.OrderItem order;
OrderItem(this.order);
#override
_OrderItemState createState() => _OrderItemState();
}
class _OrderItemState extends State<OrderItem> {
var _expanded = false;
var mycolor = Colors.white;
#override
Widget build(BuildContext context) {
ColorChanger _color = Provider.of<ColorChanger>(context);
var listProducts = widget.order.products;
return Card(
color: widget.order.orderStatus=='completed'
?Colors.lightGreen:widget.order.orderStatus=='inprogress'?
Colors.orangeAccent:
widget.order.orderStatus=='cancelled'?Colors.grey:mycolor,
margin: EdgeInsets.all(10),
child: Column(
children: <Widget>[
ListTile(
title: RichText(
text: new TextSpan(
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
new TextSpan(
text: 'Order Number : ',
style: new TextStyle(fontWeight: FontWeight.bold)),
new TextSpan(text: widget.order.uniqueOrderNumber),
],
),
),
trailing: IconButton(
icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
onPressed: () {
setState(() {
_expanded = !_expanded;
});
},
),
onLongPress: toggleSelection,
),
],
),
);
}
void toggleSelection() {
ColorChanger _color = Provider.of<ColorChanger>(context,listen:false);
Widget completeOrder = FlatButton(
child: Text('Completed'),
onPressed: () async {
try {
Navigator.of(context).pop(true);
// setState(() {
_color.setTheme(Colors.lightGreen);
// });
await Provider.of<Orders>(context, listen: false)
.updateOrder(widget.order,'completed');
} catch (error) {
}
});
Widget startOrder = FlatButton(
child: Text('In progress'),
onPressed: () async {
try {
Navigator.of(context).pop(true);
// setState(() {
_color.setTheme(Colors.orangeAccent);
//});
//Update Db to mark order in progress
await Provider.of<Orders>(context, listen: false)
.updateOrder(widget.order,'inprogress');
} catch (error) {
}
});
Widget cancelOrder = FlatButton(
child: Text('Cancel'),
onPressed: () async {
try {
Navigator.of(context).pop(false);
// setState(() {
_color.setTheme(Colors.grey);
// });
//Update Db to mark order as cancelled
await Provider.of<Orders>(context, listen: false)
.updateOrder(widget.order,'cancelled');
} catch (error) {
}
});
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text('Take Action'),
content: Text('What do you want to do with the order?'),
actions: <Widget>[
startOrder,
completeOrder,
cancelOrder
],
),
);
});
}
}
SECOND TRY based on Loren's answer.
import 'package:flutter/material.dart';
class ColorChanger with ChangeNotifier{
Color color = Colors.white;
setTheme (Color newColor) {
color = newColor;
notifyListeners();
}
}
class OrderItem extends StatefulWidget {
final ord.OrderItem order;
OrderItem(this.order);
#override
_OrderItemState createState() => _OrderItemState();
}
class _OrderItemState extends State<OrderItem> {
var _expanded = false;
//Set the color based on what was last saved in the DB
void didChangeDependencies() async {
var colorChanger = Provider.of<ColorChanger>(context, listen: false);
if(widget.order.orderStatus=='completed')
colorChanger.setTheme(Colors.lightGreen);
else if(widget.order.orderStatus=='inprogress')
colorChanger.setTheme(Colors.orangeAccent);
else if(widget.order.orderStatus=='cancelled')
colorChanger.setTheme(Colors.grey);
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
var listProducts = widget.order.products;
return Consumer<ColorChanger>(
builder: (context, colorChanger, child) {
return Card(
color: widget.order.orderStatus=='completed'
?Colors.lightGreen:widget.order.orderStatus=='inprogress'?
Colors.orangeAccent:
widget.order.orderStatus=='cancelled'?Colors.grey:mycolor,
margin: EdgeInsets.all(10),
child: Column(
children: <Widget>[
ListTile(
title: RichText(
text: new TextSpan(
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
new TextSpan(
text: 'Order Number : ',
style: new TextStyle(fontWeight: FontWeight.bold)),
new TextSpan(text: widget.order.uniqueOrderNumber),
],
),
),
trailing: IconButton(
icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
onPressed: () {
setState(() {
_expanded = !_expanded;
});
},
),
onLongPress: toggleSelection,
),
],
),
)};
}
void toggleSelection() {
ColorChanger _color = Provider.of<ColorChanger>(context,listen:false);
Widget completeOrder = FlatButton(
child: Text('Completed'),
onPressed: () async {
try {
Navigator.of(context).pop(true);
// setState(() {
_color.setTheme(Colors.lightGreen);
// });
await Provider.of<Orders>(context, listen: false)
.updateOrder(widget.order,'completed');
} catch (error) {
}
});
Widget startOrder = FlatButton(
child: Text('In progress'),
onPressed: () async {
try {
Navigator.of(context).pop(true);
// setState(() {
_color.setTheme(Colors.orangeAccent);
//});
//Update Db to mark order in progress
await Provider.of<Orders>(context, listen: false)
.updateOrder(widget.order,'inprogress');
} catch (error) {
}
});
Widget cancelOrder = FlatButton(
child: Text('Cancel'),
onPressed: () async {
try {
Navigator.of(context).pop(false);
// setState(() {
_color.setTheme(Colors.grey);
// });
//Update Db to mark order as cancelled
await Provider.of<Orders>(context, listen: false)
.updateOrder(widget.order,'cancelled');
} catch (error) {
}
});
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text('Take Action'),
content: Text('What do you want to do with the order?'),
actions: <Widget>[
startOrder,
completeOrder,
cancelOrder
],
),
);
});
}
}
When I do it this way, it changes the color of all the cards instead of just that one card. What am I doing wrong here?
Sharing order.dart
class OrderItem {
final String id;
final double amount;
final int deliveryFee;
final List<CartItem> products;
final DateTime dateTime;
final String deliveryMethod;
final String uniqueOrderNumber;
final String orderStatus;
final String userId;
final String customMessage;
final String customerName;
final String phoneNumber;
OrderItem(
{#required this.id,
#required this.amount,
#required this.products,
#required this.dateTime,
#required this.deliveryMethod,
#required this.uniqueOrderNumber,
#required this.isOrderComplete,
this.orderStatus,
#required this.customMessage,
#required this.deliveryFee,
this.customerName,
this.phoneNumber,
#required this.userId});
}
class Orders with ChangeNotifier {
final String authToken;
final String userId;
Orders(this.authToken, this.userId);
List<OrderItem> _orders = [];
List<OrderItem> get orders {
return [..._orders];
}
Future<void> updateOrder(OrderItem order,String orderStatus) async {
final id = order.id;
final customerId = order.userId;
final url =
'https://cv.firebaseio.com/orders/$customerId/$id.json?auth=$authToken';
try {
await http.patch(url,
body: json.encode({
'orderStatus':orderStatus
}));
} catch (error) {
print(error);
}
notifyListeners();
}
UPDATED ANSWER:
So when trying to do this with Provider I kept getting errors that would have required me to keep bugging you for more and more code to try and replicate everything you have going on, and I didn't want to get into that.
So this solution may or may not be acceptable to you because it uses GetX State Management, but it works. In addition it doesn't require wrapping your whole app in provider widgets so dealing with scope etc...is a non issue.
Let's add a statusColor property to your OrderItem model. This is what will get changed.
Color statusColor = Colors.white; // or whatever you you want the default color to be
Your updated Orders class that uses GetX instead of ChangeNotifier (again, not because Provider can't do this, but because I was dealing with too many errors and frankly GetX is easier in my opinion anyway)
class Orders extends GetxController {
final String authToken;
final String userId;
Orders(this.authToken, this.userId);
List<OrderItem> orders = []; // back to what I said earlier about no point in getters and setters here
// temp function just to test this on my end
void addOrder(OrderItem order) {
orders.add(order);
update();
}
// this loops through the list to find the matching order number,
// then updates the color for just that order
void updateOrderStatusColor({OrderItem updatedOrder, String status}) {
for (final order in orders) {
if (order.uniqueOrderNumber == updatedOrder.uniqueOrderNumber) {
switch (status) {
case 'completed':
{
order.statusColor = Colors.greenAccent;
}
break;
case 'inprogress':
{
order.statusColor = Colors.orangeAccent;
}
break;
case 'cancelled':
{
order.statusColor = Colors.grey;
}
break;
}
}
}
update(); // equivelent of notifyListeners();
}
// ...the rest of your class
}
A few small changes to your card. didChangeDependencies can go away entirely.
// it seems like you had 2 classes with the same name, which is not recommended
class OrderItemCard extends StatefulWidget {
final OrderItem order;
OrderItemCard(this.order);
#override
_OrderItemCardState createState() => _OrderItemCardState();
}
class _OrderItemCardState extends State<OrderItemCard> {
var _expanded = false;
final controller = Get.find<Orders>(); // equivilent of Provider.of... finds the same instance without needing context
void toggleSelection() {
Widget completeOrder = TextButton(
child: Text('Completed'),
onPressed: () async {
try {
Navigator.of(context).pop(true);
controller.updateOrderStatusColor(
updatedOrder: widget.order, status: 'completed'); // calling new function here
} catch (error) {}
});
Widget startOrder = FlatButton(
child: Text('In progress'),
onPressed: () async {
try {
Navigator.of(context).pop(true);
controller.updateOrderStatusColor(
updatedOrder: widget.order, status: 'inprogress');
} catch (error) {}
});
Widget cancelOrder = FlatButton(
child: Text('Cancel'),
onPressed: () async {
controller.updateOrderStatusColor(
updatedOrder: widget.order, status: 'cancelled');
try {
Navigator.of(context).pop(false);
} catch (error) {}
});
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text('Take Action'),
content: Text('What do you want to do with the order?'),
actions: <Widget>[startOrder, completeOrder, cancelOrder],
),
);
}
#override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(10),
color: widget.order.statusColor, // new color property added to your model
child: Column(
children: <Widget>[
ListTile(
title: RichText(
text: new TextSpan(
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
new TextSpan(
text: 'Order Number : ${widget.order.uniqueOrderNumber} ',
style: new TextStyle(fontWeight: FontWeight.bold)),
],
),
),
trailing: IconButton(
icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
onPressed: () {
setState(() {
_expanded = !_expanded;
});
},
),
onLongPress: toggleSelection,
),
],
),
);
}
}
Not sure what you have going on in your UI but here's a quick demo of how it would work in GetX. It's a simple ListView.builder populated from the orders list from the GetX Class. The GetBuilder<Orders> widget rebuilds when update() is called. Also a simple button that adds a dummy item for demo purposes. I don't know how you're generating your unique order # but I'm just using the list index for this. Both inside a column within a scaffold on a demo page.
// Equivilent of Consumer but doesn't need context nor any provider widget above it
GetBuilder<Orders>(
builder: (controller) => Expanded(
child: ListView.builder(
itemCount: controller.orders.length,
itemBuilder: (context, index) =>
OrderItemCard(controller.orders[index])),
),
),
TextButton(
onPressed: () {
final controller = Get.find<Orders>();
final orderItem = OrderItem(
orderStatus: ' ',
uniqueOrderNumber: controller.orders.length
.toString(), // just a hack to generate a unique order # for demo
);
controller.addOrder(orderItem);
},
child: Text('Add Item'),
)
Last thing is just initializing the GetX Controller. It can be done anywhere as long as its before you try and use it.
void main() {
// initialing the GetX GetxController
// not sure how you're generating the required auth and user id
// but I'm just passing in empty strings for now
Get.put(Orders('', ''));
runApp(MyApp());
}
So if you're open to GetX here, you can leave Provider for any other ChangeNotifier classes you may have in place if you want. For this you would just need to replace any Consumer<Orders> with GetBuilder<Order> and then get rid of the Provider<Orders>(create:... widget entirely.
OLD ANSWER:
You're missing a couple things in order to be using Provider properly and get the color changing the way you want.
For starters, your Card needs to be wrapped in a Consumer widget that gets notified of changes and rebuilds its children. Inside the Consumer, you need to be using the color property of the ChangeNotifier class. It doesn't need to know or care about the orderStatus because you're already explicitly telling it to change color when you call the setTheme method.
Consumer<ColorChanger>( // this is what rebuilds and changes the color
builder: (context, colorChanger, child) {
return Card(
color: colorChanger.color, // colorChanger here is equivalent of declaring final colorChanger = Provider.of<ColorChanger>(context...
child: Column(
children: <Widget>[
ListTile(
title: RichText(
text: new TextSpan(
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
new TextSpan(
text: 'Order Number : ',
style: new TextStyle(fontWeight: FontWeight.bold)),
new TextSpan(text: widget.order.uniqueOrderNumber),
],
),
),
trailing: IconButton(
icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
onPressed: () {
setState(() {
_expanded = !_expanded;
});
},
),
onLongPress: toggleSelection,
),
],
),
);
});
Next, see this link as to why you're not gaining anything with using the private _color and public getColor in your ChangeNotifier class.
So lets simplify that a bit.
class ColorChanger with ChangeNotifier {
Color color = Colors.white;
ColorChanger(this.color);
setTheme(Color newColor) {
color = newColor;
notifyListeners();
}
}
Now, whenever you call the setTheme function from your dialog, that card will change to whatever color you pass into it because the Consumer widget is notified, and will rebuild with the updated color value of the ChangeNotifier class.
Something like this would be the simplest way to the thing you want to achieve:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// define a list of colors:
final colors = <Color>[
Colors.white, // this is the inital color
Colors.green,
Colors.orange,
Colors.grey
];
int index = 0;
Future<int> showMyDialog(BuildContext context) async {
// Since all Navigator.push(...) and showDialog(...) calls are futures
// we can send values alongside them when we pop the context:
// final value = await Navigator.push(...);
// or
// final value = await showDialog(...);
// then we do a:
// Navigator.pop(context, SOME_VALUE,);
// the value variable will be assigned to the one we sent
return await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Take Action'),
content: Text('What do you want to do with the order?'),
actions: <Widget>[
TextButton(
child: Text('Completed',
style: TextStyle(
color: Colors.green,
)),
onPressed: () => Navigator.pop(context, 1)),
TextButton(
child: Text('In progress',
style: TextStyle(
color: Colors.orange,
)),
onPressed: () => Navigator.pop(context, 2)),
TextButton(
child: Text('Cancel',
style: TextStyle(
color: Colors.grey,
)),
onPressed: () => Navigator.pop(context, 3)),
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: <Widget>[
Card(
color: colors[index],
child: Container(width: 50, height: 50),
),
ElevatedButton(
child: Text('Show dialog'),
onPressed: () async {
// call the showMyDialog function, it returns
// a future int so we have to await it
final int _index = await showMyDialog(context);
// if the returned value (_index) is null we use
// the old one value to avoid erros in the code
setState(() => index = _index ?? index);
}),
]),
);
}
}
A very simple workaround would be to declare a global color variable cardColor and assign it to the color property of the card. Then on the alertdialog, change the 'onChange'or 'onTap' property of the widget so that on tapping, the widget changes the value of the global variable cardColor to a different color. Don't forget to do the final step i.e. changing the value of the variable, inside setState()
The best way to achieve it by using AwesomeDialog
https://pub.dev/packages/awesome_dialog
AwesomeDialog(
context: context,
dialogType: DialogType.INFO,
animType: AnimType.BOTTOMSLIDE,
title: 'Dialog Title',
desc: 'Dialog description here.............',
btnCancelOnPress: () {},
btnOkOnPress: () {},
)..show();
class Cari extends AnaState implements InterHttp {
String token = Tokenlar.token();
Future<Void> getCariHareket() async {
final response = await get(
"http://148.111.156.214:36555/Api/Customers/CustomerActionById/3",
headers: {HttpHeaders.authorizationHeader: "bearer $token",HttpHeaders.acceptHeader: "application/json"},
);
if (response.statusCode == 200)
{
List<CariHareketListe> hareketListe=(json.decode(response.body) as List).map((i) =>
CariHareketListe.fromJson(i)).toList();
//Map<String,dynamic> map = json.decode(response.body);
setState(() {
provider = hareketListe;
});
}
else
{
throw Exception('Cari hareket listesi yükleme başarısız oldu');
}
}
}
i want to be able to call setstate in this class but it gives me this error:
FlutterError (setState() called in constructor: Cari#ed627(lifecycle state: created, no widget, not mounted)
This happens when you call setState() on a State object for a widget that hasn't been inserted into the widget tree yet. It is not necessary to call setState() in the constructor, since the state is already assumed to be dirty when it is initially created.)
This is the main dart i want to setstate in cari then i want that data go into Icwidgetlar to be able to Return a list view to Anastate
import 'package:flutter/material.dart';
import 'package:guven_avize/ApiMetodlar/CariMetod.dart';
import 'Veriler/CariVeriler.dart';
class Anamenu
{
Anamenu()
{
}
}
class Ana extends StatefulWidget{
#override
AnaState createState() => AnaState();
}
class IcWidgetlar
{
List<CariHareketListe> provider = new List<CariHareketListe>();
ListView icyapi(int secilenTab)
{
if (secilenTab == 0)
{
Cari cari = new Cari();
cari.veriCek(1);
ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: provider.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
color: Colors.amber,
child: Center(child: Text('Entry ${provider[index]}')),
);
}
);
}
}
}
class AnaState extends State<Ana> with IcWidgetlar {
IcWidgetlar widgetlar = new IcWidgetlar();
int selectedIndex = 0;
static const TextStyle optionStyle = TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Cari',
style: optionStyle,
),
Text(
'Stok',
style: optionStyle,
),
Text(
'Sipariş',
style: optionStyle,
),
];
void _onItemTapped(int index) {
setState(() {
selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: () async => false,
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Color.fromRGBO(106, 112, 222, 50),
title: _widgetOptions.elementAt(selectedIndex),
),
body: widgetlar.icyapi(selectedIndex),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.face),
title: Text('Cari'),
),
BottomNavigationBarItem(
icon: Icon(Icons.inbox),
title: Text('Stok'),
),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart),
title: Text('Sipariş'),
),
],
currentIndex: selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
));
}
}
Why are you not returning the result instead?
Instead of returning Future, return Future>, and get it from other class.
You can even use FutureBuilder to wait until the response arrive.
Call the function in initState
#override
void initState() {
super.initState();
getCariHareket().then( (list) {
setState((){
provider = list;
}())
});
}
I need to shape myUrl inside Future related to username which I got from myfirstpage, I can get the name and use it in my homepage title but I couldn't figured out how can I use it in myUrl(instead of "$_name"),
Probably I made a mistake with point to data with "pushNamed(MyHomePage.routeName);" actually I don't need that value in MyHomePage, I just need it for shape myUrl line, also I tried to make "_name" value as a global value etc just not couldn't succeed..
import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
new GlobalKey<RefreshIndicatorState>();
Future<bool> saveNamedPreference(String name) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString("name", name);
return prefs.commit();
}
Future<String> getNamePreferences() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String name = prefs.getString("name");
return name;
}
Payload payloadFromJson(String str) {
return Payload.fromJson(json.decode(str));
}
String payloadToJson(Payload data) {
return json.encode(data.toJson());
}
Future<Payload> getData() async{
String myUrl = 'http://lunedor.pythonanywhere.com/query?username=$_name';
http.Response response = await http.get(myUrl);
print(myUrl);
return response == null ? getData() : payloadFromJson(response.body);
}
class Payload {
String moviecast;
String moviedirectors;
String moviegenre;
String movieposterurl;
String movierating;
String movieruntime;
String moviesummary;
String movietitle;
String moviewriters;
String movieyear;
Payload({
this.moviecast,
this.moviedirectors,
this.moviegenre,
this.movieposterurl,
this.movierating,
this.movieruntime,
this.moviesummary,
this.movietitle,
this.moviewriters,
this.movieyear,
});
factory Payload.fromJson(Map<String, dynamic> json) => Payload(
moviecast: json["Actors"],
moviedirectors: json["Director"],
moviegenre: json["Genre"],
movieposterurl: json["Poster"],
movierating: json["imdbRating"],
movieruntime: json["Runtime"],
moviesummary: json["Plot"],
movietitle: json["Title"],
moviewriters: json["Writer"],
movieyear: json["Year"],
);
Map<String, dynamic> toJson() => {
"moviecast": moviecast,
"moviedirectors": moviedirectors,
"moviegenre": moviegenre,
"movieposterurl": movieposterurl.replaceAll('300.jpg', '900.jpg'),
"movierating": movierating,
"movieruntime": movieruntime,
"moviesummary": moviesummary,
"movietitle": movietitle,
"moviewriters": moviewriters,
"movieyear": movieyear,
};
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Random Movie',
theme: new ThemeData(
primarySwatch: Colors.grey,
),
home: new MyFirstPage(),
routes: <String, WidgetBuilder>{
MyHomePage.routeName: (context) => new MyHomePage(),
},
);
}
}
class MyFirstPage extends StatefulWidget {
#override
_MyFirstPageState createState() => new _MyFirstPageState();
}
class _MyFirstPageState extends State<MyFirstPage>{
var _controller = new TextEditingController();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
backgroundColor: Colors.blueGrey,
centerTitle: true,
title: new Text("Please enter your Trakt username",
style: new TextStyle(
fontSize: 18.0,
color: Colors.white,
),
),
),
body: new ListView(
children: <Widget>[
new ListTile(
title: new TextField(
controller: _controller,
),
),
new ListTile(
title: new RaisedButton(
child: new Text("Submit"),
onPressed:(){setState(() {
saveName();
});
}),
)
],
),
);
}
void saveName() {
String name = _controller.text;
saveNamedPreference(name).then((bool committed) {
Navigator.of(context).pushNamed(MyHomePage.routeName);
});
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
static String routeName = "/myHomePage";
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
Payload payload;
class _MyHomePageState extends State<MyHomePage> {
Modal modal = Modal();
bool isLoading = true;
String _name = "";
#override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {loadData();
WidgetsBinding.instance.addPostFrameCallback((_) => _refreshIndicatorKey.currentState.show());
getNamePreferences().then(updateName);
});
}
void loadData() async {
payload = await getData();
isLoading = false;
setState(() {});
print('${payload.movieposterurl.replaceAll('300.jpg', '900.jpg')}');
}
void updateName(String name) {
setState(() {
this._name = name;
ValueKey(_name);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar:
AppBar(
backgroundColor: Colors.blueGrey,
title:
isLoading ? Center(child: CircularProgressIndicator()):Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Center(
child: Text('${payload.movietitle}', maxLines: 2, textAlign: TextAlign.center,
style: new TextStyle(
fontSize: 18.0,
color: Colors.white,
),
),
),
Text('${payload.movieyear}' + " - " + _name + " - " + '${payload.movierating}',
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
fontStyle: FontStyle.italic,),
),
]
),
),
),
body:
isLoading ? Center(child: CircularProgressIndicator()):
RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: () async{payload = await getData();
isLoading = false;
setState(() {});
},
child: Center(
child:ListView(
shrinkWrap: true,
children: [
FittedBox(
alignment: Alignment.center,
child:
Image.network('${payload.movieposterurl.replaceAll('300.jpg', '900.jpg')}'),
),
]
)
)
),
bottomNavigationBar: isLoading ? Center(child: CircularProgressIndicator()):
BottomAppBar(
child: Container(
color: Colors.grey,
child: SizedBox(
width: double.infinity,
child:
FlatButton(
color: Colors.grey,
textColor: Colors.black,
onPressed: () {
modal.mainBottomSheet(context);
},
child: Text("Details",
style: TextStyle(fontSize: 16.0),),
),
),
),
),
);
}
}
class Modal {
mainBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
_createTile(
context, "Genre: " + payload.moviegenre + "\n" + "Runtime: " + payload.movieruntime, Icons.local_movies,
_action),
_createTile(
context, "Director: " + payload.moviedirectors,
Icons.movie_creation,
_action),
_createTile(
context, "Writer: " + payload.moviewriters,
Icons.movie,
_action),
_createTile(
context, "Cast: " + payload.moviecast, Icons.chrome_reader_mode,
_action),
_createTile(
context, "Summary: " + payload.moviesummary, Icons.recent_actors, _action),
],
),
);
}
);
}
ListTile _createTile(BuildContext context, String name, IconData icon,
Function action) {
return ListTile(
leading: Icon(icon),
title: Text(name),
onTap: () {
Navigator.pop(context);
action();
},
);
}
_action() {
print('action');
}
}
Not completely sure I got your question, but it should be enough to modify your getData() method to accept a String in parameters, at the moment getData() is a top-level function that doesn't know the _name value because is a private instance variable of _MyHomePageState
Future<Payload> getData(String name) async{
String myUrl = 'http://lunedor.pythonanywhere.com/query?username=$name';
http.Response response = await http.get(myUrl);
print(myUrl);
return response == null ? getData() : payloadFromJson(response.body);
}
And then in your loadData() method pass the correct value
void loadData() async {
payload = await getData(_name);
isLoading = false;
setState(() {});
print('${payload.movieposterurl.replaceAll('300.jpg', '900.jpg')}');
}
One last thing, you should add the "reload" logic when you change the name
void updateName(String name) {
setState(() {
isLoading = true;
this._name = name;
ValueKey(_name);
loadData();
});
}
Personally I think that the Payload variable should stay inside your _MyHomePageState class
I'm trying to return the quote section from this api located here: https://api.quotable.io/random
The json looks like this:
{"_id":"9hIehvX23pvr","content":"There is no charm equal to tenderness
of heart.","author":"Jane Austen"}
Here is the part of the code that connects to the API.
Future<String> _getQuote() async {
final res = await http.get('https://api.quotable.io/random');
return json.decode(res.body);
}
Whenever I run the app, I get this error that tells me it's getting a null. But I know the api works.
_FutureBuilderState#b8df9): I/flutter ( 5315): A non-null String must be provided to a Text widget. I/flutter ( 5315):
'package:flutter/src/widgets/text.dart': I/flutter ( 5315): Failed
assertion: line 269 pos 10: 'data != null'
I just need the "content" part of the json. How would I parse just that part?
Thanks!
#SkyeBoniwell, not getting any response with the api. Sure it's working?
Screenshot:
You can copy paste run full code below
Step1 : add <uses-permission android:name="android.permission.INTERNET"/> to AndroidManifest
Step2 : Use the following class to parse
Demo demoFromJson(String str) => Demo.fromJson(json.decode(str));
String demoToJson(Demo data) => json.encode(data.toJson());
class Demo {
String id;
String content;
String author;
Demo({
this.id,
this.content,
this.author,
});
factory Demo.fromJson(Map<String, dynamic> json) => Demo(
id: json["_id"],
content: json["content"],
author: json["author"],
);
Map<String, dynamic> toJson() => {
"_id": id,
"content": content,
"author": author,
};
}
working demo
full working code
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';
Demo demoFromJson(String str) => Demo.fromJson(json.decode(str));
String demoToJson(Demo data) => json.encode(data.toJson());
class Demo {
String id;
String content;
String author;
Demo({
this.id,
this.content,
this.author,
});
factory Demo.fromJson(Map<String, dynamic> json) => Demo(
id: json["_id"],
content: json["content"],
author: json["author"],
);
Map<String, dynamic> toJson() => {
"_id": id,
"content": content,
"author": author,
};
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FutureBuilderWidget(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
String url = ' http://api.quotable.io/random';
///Method for GET Request
Future<Demo> getDemoResponse() async {
final response = await http.get('https://api.quotable.io/random');
print('response ${response}');
if (response.statusCode == 200) {
print('response body${response.body}');
return demoFromJson(response.body);
} else {
throw Exception('Failed to load ');
}
}
class FutureBuilderWidget extends StatefulWidget {
#override
_FutureBuilderWidgetState createState() => _FutureBuilderWidgetState();
}
class _FutureBuilderWidgetState extends State<FutureBuilderWidget> {
bool _isButtonClicked = false;
var _buttonIcon = Icons.cloud_download;
var _buttonText = "Fetch Data";
var _buttonColor = Colors.green;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Container(
child: Center(
child: Text(
'Future Builder Widget',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
//fontFamily: Utils.ubuntuRegularFont
),
),
),
margin: EdgeInsets.only(right: 48),
),
),
body: Center(
child: FutureBuilder<Demo>(
///If future is null then API will not be called as soon as the screen
///loads. This can be used to make this Future Builder dependent
///on a button click.
future: _isButtonClicked ? getDemoResponse() : null,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
///when the future is null
case ConnectionState.none:
return Text(
'Press the button to fetch data',
textAlign: TextAlign.center,
);
case ConnectionState.active:
///when data is being fetched
case ConnectionState.waiting:
return CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue));
case ConnectionState.done:
///task is complete with an error (eg. When you
///are offline)
if (snapshot.hasError)
return Text(
'Error:\n\n${snapshot.error}',
textAlign: TextAlign.center,
);
///task is complete with some data
return Text(
'Fetched Data:\n\n${snapshot.data.content}',
textAlign: TextAlign.center,
);
}
},
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: FloatingActionButton.extended(
backgroundColor: _buttonColor,
onPressed: () {
///Calling method to fetch data from the server
//getDemoResponse();
///You need to reset UI by calling setState.
setState(() {
_isButtonClicked == false
? _isButtonClicked = true
: _isButtonClicked = false;
if (!_isButtonClicked) {
_buttonIcon = Icons.cloud_download;
_buttonColor = Colors.green;
_buttonText = "Fetch Data";
} else {
_buttonIcon = Icons.replay;
_buttonColor = Colors.deepOrange;
_buttonText = "Reset";
}
});
},
icon: Icon(
_buttonIcon,
color: Colors.white,
),
label: Text(
_buttonText,
style: TextStyle(color: Colors.white),
),
),
);
}
}