Flutter - Refresh widget with button press - flutter

I have an app that pulls quotes from an api and displays it.
I am struggling to get my refresh button to work. When the refresh button is pressed a new quote should load.
The current code I have for the page is:
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:share_plus/share_plus.dart';
import './Quote.dart';
class QuoteData extends StatefulWidget {
const QuoteData({Key? key}) : super(key: key);
#override
_QuoteDataState createState() => _QuoteDataState();
}
// call the API and fetch the response
Future<Quote> fetchQuote() async {
final response = await http.get(Uri.parse('https://favqs.com/api/qotd'));
if (response.statusCode == 200) {
return Quote.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load Quote');
}
}
class _QuoteDataState extends State<QuoteData>
with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
late Future<Quote> quote;
late Future<List<Quote>> wholeQuotes;
#override
void initState() {
super.initState();
quote = fetchQuote();
}
#override
Widget build(BuildContext context) {
super.build(context);
return FutureBuilder<Quote>(
future: quote,
builder: (context, snapshot) {
if (snapshot.hasData) {
return SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.only(left: 50.0),
child: Text(
snapshot.data!.quoteText,
style: const TextStyle(
fontSize: 30.0,
color: Colors.white,
fontFamily: 'quoteScript'),
),
),
const SizedBox(
height: 20.0,
),
Text(
'-${snapshot.data!.quoteAuthor}-',
style: const TextStyle(
fontSize: 23.0,
color: Colors.white,
fontFamily: 'quoteScript'),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
//share button
IconButton(
icon: const Icon(
Icons.share,
color: Colors.white,
),
onPressed: () {
Share.share(
'${snapshot.data!.quoteText}--${snapshot.data!.quoteAuthor}');
},
),
//refresh button
IconButton(
icon: const Icon(
Icons.share,
color: Colors.white,
),
onPressed: () {
fetchQuote();
},
),
],
),
],
),
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return const Center(child: CircularProgressIndicator());
},
);
}
}
How can I get the refresh button to refresh the page and load a new quote from the api and display it?
Any help would be appreciated.

Reset future using setState.
Future<Quote> quote;
...
IconButton(
...
onPressed: () {
setState(() {
quote = fetchQuote();
});
},
)

Did you try to use setState under onPressed()? You can do something like:
onPressed: () {
setState(()
Share.share(
'${snapshot.data!.quoteText}--${snapshot.data!.quoteAuthor}');
)},

Related

How to change Elevated button's title and color onpressed

I'm developing an app to make meal reservations, and I have an ElevatedButton that opens an alert when pressed. This alert is where the user is able to confirm the reservation. So, the alert has 2 text buttons, and I need that when the "sim" button is pressed, the ElevatedButton changes from "Reservar" with green color to "Cancelar Reserva" with red color.
I tried this way but it doesn't work:
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import '../components/meal_item.dart';
import '../models/day_of_week.dart';
import '../models/want_to_comment.dart';
import '../models/meal.dart';
import '../utils/app_routes.dart';
import '../data/dummy_data.dart';
enum StatusReserva { y, n }
Color statusToColor(StatusReserva value) {
Color color = Colors.green;
switch (value) {
case StatusReserva.n:
break;
case StatusReserva.y:
color = Colors.red;
break;
}
return color;
}
String statusToString(StatusReserva value) {
String title = 'Reservar';
switch (value) {
case StatusReserva.n:
break;
case StatusReserva.y:
title = 'Cancelar Reserva';
break;
}
return title;
}
class DaysOfWeekMealsScreen extends StatefulWidget {
final List<Meal> meals;
final StatusReserva status;
final Function(StatusReserva) onStatusChanged;
const DaysOfWeekMealsScreen({
Key? key,
required this.meals,
required this.status,
required this.onStatusChanged,
}) : super(key: key);
#override
State<DaysOfWeekMealsScreen> createState() => _DaysOfWeekMealsScreenState();
}
class _DaysOfWeekMealsScreenState extends State<DaysOfWeekMealsScreen> {
StatusReserva status = StatusReserva();
#override
void initState() {
super.initState();
status = widget.status;
}
#override
Widget build(BuildContext context) {
final dayOfWeek = ModalRoute.of(context)!.settings.arguments as DayOfWeek;
final dayOfWeekMeals = widget.meals.where((meal) {
return meal.days_of_week.contains(dayOfWeek.id);
}).toList();
void _incrementCount(BuildContext context) {
dayOfWeek.count++;
}
Future<void> _showMyDialog(BuildContext context) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text(
' ',
),
content: SingleChildScrollView(
child: ListBody(
children: const <Widget>[
Text(
'Confirmar reserva para o dia XX/XX/XX?',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
fontFamily: 'Raleway',
fontWeight: FontWeight.bold),
),
],
),
),
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
child: const Text('Sim'),
onPressed: () => {
_incrementCount,
Navigator.pop(context),
status = StatusReserva.y
},
),
TextButton(
child: const Text('Não'),
onPressed: () => Navigator.pop(context),
),
],
),
],
);
},
);
}
return Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Image.asset(
'assets/images/logo.png',
fit: BoxFit.contain,
height: 32,
),
Container(
padding: const EdgeInsets.all(8.0),
child: Text(dayOfWeek.title)),
],
),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
margin: const EdgeInsets.all(10),
child: ElevatedButton(
onPressed: () => {_showMyDialog(context)},
style: ButtonStyle(
shape: MaterialStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
)),
padding: MaterialStateProperty.all(const EdgeInsets.all(15)),
backgroundColor:
MaterialStateProperty.all(statusToColor(widget.status)),
),
child: Text(statusToString(widget.status)),
),
),
Expanded(
child: ListView.builder(
itemCount: dayOfWeekMeals.length,
itemBuilder: (ctx, index) {
return MealItem(dayOfWeekMeals[index]);
},
),
),
],
),
);
}
}
import 'package:apetit_project/models/want_to_comment.dart';
import 'package:apetit_project/screens/login_screen.dart';
import 'package:flutter/material.dart';
import 'screens/tabs_screen.dart';
import 'screens/days_of_week_meals_screen.dart';
import 'screens/meal_detail_screen.dart';
import 'screens/settings_screen.dart';
import 'screens/want_to_comment_screen.dart';
import 'screens/comment_screen.dart';
import 'utils/app_routes.dart';
import 'models/meal.dart';
import 'models/settings.dart';
import 'data/dummy_data.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Settings settings = Settings();
StatusReserva status = StatusReserva();
List<Meal> _gourmetMeals = [];
List<Meal> _lightMeals = [];
void _filterMeals(Settings settings) {
setState(() {
this.settings = settings;
});
}
void _reserveMeals(StatusReserva status) {
setState(() {
this.status = status;
});
}
void _toggleGourmet(Meal meal) {
setState(() {
_gourmetMeals.contains(meal)
? _gourmetMeals.remove(meal)
: _gourmetMeals.add(meal);
});
}
bool _isGourmet(Meal meal) {
return _gourmetMeals.contains(meal);
}
void _toggleLight(Meal meal) {
setState(() {
_lightMeals.contains(meal)
? _lightMeals.remove(meal)
: _lightMeals.add(meal);
});
}
bool _isLight(Meal meal) {
return _lightMeals.contains(meal);
}
#override
Widget build(BuildContext context) {
final ThemeData tema = ThemeData(
fontFamily: 'Raleway',
textTheme: ThemeData.light().textTheme.copyWith(
headline6: const TextStyle(
fontSize: 20,
fontFamily: 'Raleway',
fontWeight: FontWeight.bold,
),
),
);
return MaterialApp(
title: 'Appetit',
theme: tema.copyWith(
colorScheme: tema.colorScheme.copyWith(
primary: const Color.fromRGBO(222, 1, 59, 1),
secondary: const Color.fromRGBO(240, 222, 77, 1),
),
),
routes: {
AppRoutes.LOGIN: (ctx) => LoginScreen(),
AppRoutes.HOME: (ctx) => TabsScreen(_gourmetMeals, _lightMeals),
AppRoutes.WANT_TO_COMMENT: (ctx) => const WantToCommentScreen(),
AppRoutes.COMMENT: (ctx) => const CommentScreen(),
AppRoutes.DAYS_OF_WEEK_MEALS: (ctx) => DaysOfWeekMealsScreen(
meals: DUMMY_MEALS,
status,
_reserveMeals,
),
AppRoutes.MEAL_DETAIL: (ctx) => MealDetailScreen(
_toggleGourmet, _isGourmet, _toggleLight, _isLight),
AppRoutes.SETTINGS: (ctx) => SettingsScreen(settings, _filterMeals),
},
);
}
createMaterialColor(Color color) {}
}
To change the state of a widget you can use setState
enum StatusReserva { y, n }
Color statusToColor(StatusReserva value) {
Color color = Colors.green;
switch (value) {
case StatusReserva.n:
break;
case StatusReserva.y:
color = Colors.red;
break;
}
return color;
}
class StatusTest extends StatefulWidget {
const StatusTest({Key? key}) : super(key: key);
#override
State<StatusTest> createState() => _StatusTestState();
}
class _StatusTestState extends State<StatusTest> {
var status = StatusReserva.n;
Future<void> _showMyDialog(BuildContext context) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text(
' ',
),
content: SingleChildScrollView(
child: ListBody(
children: const <Widget>[
Text(
'Confirmar reserva para o dia XX/XX/XX?',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
fontFamily: 'Raleway',
fontWeight: FontWeight.bold),
),
],
),
),
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
child: const Text('Sim'),
onPressed: () => setState(() {
Navigator.pop(context);
status = StatusReserva.n;
}),
),
TextButton(
child: const Text('Não'),
onPressed: () => setState(() {
Navigator.pop(context);
status = StatusReserva.y;
}),
),
],
),
],
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 100,
height: 100,
child: Container(
color: statusToColor(status),
child: const Center(
child: Text(
'Status color',
style: TextStyle(color: Colors.white),
)),
),
),
ElevatedButton(
onPressed: () {
_showMyDialog(context);
},
child: const Text('Show dialog'),
),
],
),
),
);
}
}
For further clarification
The StatusReserva status = StatusReserva.n; should be in the State class _DaysOfWeekMealsScreenState. And to change it use setState. This value should never change if not inside a setState.
The Sim button onPressed should be like the following (Also with fixes of an unintended Set creation):
TextButton(
child: const Text('Sim'),
onPressed: () {
_incrementCount();
Navigator.pop(context);
setState(() => status = StatusReserva.y);
},
),
Use a boolean value instead of enum like this
bool isCancelReservar = false;
and in the state class before build method add this code :
bool isCancelReservar = false;
#override
void initState(){
isCancelReservar = widget.isCancelReservar;
super.initState();
}
Now you are able to update the value of isCancelReservar on clicking "sim button" using setState method. Your text button should look like this :
TextButton(
child: const Text('Sim'),
onPressed: () {
setState(() {
isCancelReservar = true;
});
_incrementCount,
Navigator.pop(context);
},
),
and your elevated button like this :
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
margin: const EdgeInsets.all(10),
child: ElevatedButton(
onPressed: () => {_showMyDialog(context)},
style: ButtonStyle(
shape: MaterialStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
)),
padding: MaterialStateProperty.all(const EdgeInsets.all(15)),
backgroundColor:
MaterialStateProperty.all(isCancelReservar ? Colors.red :
Colors.green),
),
child: Text(isCancelReservar ? "Cancelar Reserva" : "Reservar"),
),
),
where you can see the backgroundColor will be red with text "Cancelar Reserva" if the value of isCancelReservar is true else it would be of color green with test Reservar.

Flutter: How to replace icon image from the list of pictures to chose from?

For instance: I have a main Icon so when you click on it, it opens a pop-up window with smaller icons/images to select from. So if you select one of the pictures from that pop-up it replaces the main Icon to that specific image.
I have spent hours trying to figure out how to replace icon images but nothing seems to work.
I have created an example (I have used flutter_speed_dial to make expandable buttons but it's not necessary). You can adjust it to your needs:
class _TestState extends State<Test> {
var fabIcon = Icons.expand_less;
var button1Icon = Icons.home;
var button2Icon = Icons.shop;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
floatingActionButton: SpeedDial(
icon: fabIcon,
backgroundColor: Color(0xFF801E48),
visible: true,
curve: Curves.bounceIn,
children: [
// FAB 1
SpeedDialChild(
child: Icon(button1Icon),
backgroundColor: Color(0xFF801E48),
onTap: () {
var temp = fabIcon;
setState(() {
fabIcon = button1Icon;
button1Icon = temp;
});
},
labelStyle: TextStyle(
fontWeight: FontWeight.w500,
color: Colors.white,
fontSize: 16.0),
labelBackgroundColor: Color(0xFF801E48)),
// FAB 2
SpeedDialChild(
child: Icon(button2Icon),
backgroundColor: Color(0xFF801E48),
onTap: () {
var temp = fabIcon;
setState(() {
fabIcon = button2Icon;
button2Icon = temp;
});
},
labelStyle: TextStyle(
fontWeight: FontWeight.w500,
color: Colors.white,
fontSize: 16.0),
labelBackgroundColor: Color(0xFF801E48))
],
),
),
);
}
}
Using showDialog(...) is the solution.
Hope this will help you and others.
you can look at this example:
import 'package:flutter/material.dart';
class IconDialogScreen extends StatefulWidget {
const IconDialogScreen({Key? key}) : super(key: key);
#override
State<IconDialogScreen> createState() => _IconDialogScreenState();
}
class _IconDialogScreenState extends State<IconDialogScreen> {
IconData icon = Icons.abc;
List<IconData> icons = [
Icons.abc,
Icons.person_add,
Icons.person,
Icons.person_remove,
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: TextButton(
onPressed: onIconClicked,
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 20,
children: [Icon(icon, size: 50), const Text("change icon")],
),
),
)
],
),
);
}
void onIconClicked() async {
IconData? _icon = await showDialog<IconData?>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Select one icon'),
content: Wrap(
spacing: 10,
runSpacing: 10,
children: icons.map<Widget>((e) {
return ElevatedButton(
onPressed: () {
Navigator.of(context).pop(e);
},
child: Icon(e, size: 50));
}).toList(),
),
);
},
);
if (_icon != null) {
setState(() {
icon = _icon;
});
}
}
}

Unexpected null value when try to open drawer in flutter

I am trying to open the drawer and it gives me the below error:
======== Exception caught by gesture ===============================================================
The following TypeErrorImpl was thrown while handling a gesture:
Unexpected null value.
When the exception was thrown, this was the stack:
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 236:49 throw_
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 518:63 nullCheck
packages/l7/screens/main/view_model/main_view_model.dart 15:36 openOrCloseDrawer
packages/l7/screens/main/view/components/header.dart 35:42 <fn>
packages/flutter/src/material/ink_well.dart 989:21 [_handleTap]
...
Handler: "onTap"
Recognizer: TapGestureRecognizer#ae119
debugOwner: GestureDetector
state: possible
won arena
finalPosition: Offset(35.8, 49.7)
finalLocalPosition: Offset(15.8, 12.9)
button: 1
sent tap down
====================================================================================================
and the below method is openOrCloseDrawer():
void openOrCloseDrawer() {
if (_scaffoldKey.currentState!.isDrawerOpen) {
_scaffoldKey.currentState!.openEndDrawer();
setState(ViewState.Idle);
} else {
_scaffoldKey.currentState!.openDrawer();
setState(ViewState.Idle);
}
}
related to the below ViewModel:
import 'package:flutter/material.dart';
import 'package:l7/enums/ScreenState.dart';
import 'package:l7/screens/BaseViewModel.dart';
class MainViewModel extends BaseViewModel {
int _selectedIndex = 0;
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
int get selectedIndex => _selectedIndex;
List<String> get menuItems =>
["Cases", "Services", "About Us", "Careers", "Blog", "Contact"];
GlobalKey<ScaffoldState> get scaffoldkey => _scaffoldKey;
void openOrCloseDrawer() {
if (_scaffoldKey.currentState!.isDrawerOpen) {
_scaffoldKey.currentState!.openEndDrawer();
setState(ViewState.Idle);
} else {
_scaffoldKey.currentState!.openDrawer();
setState(ViewState.Idle);
}
}
void setMenuIndex(int index) {
_selectedIndex = index;
setState(ViewState.Idle);
}
}
and this is the below BaseViewModel:
import 'package:flutter/widgets.dart';
import 'package:l7/enums/ScreenState.dart';
import 'package:l7/utils/context_extentions.dart';
class BaseViewModel extends ChangeNotifier {
ViewState _state = ViewState.Idle;
ViewState get state => _state;
SwitchState _switchState = SwitchState.CLOSE;
SwitchState get switchState => _switchState;
void setState(ViewState viewState) {
_state = viewState;
notifyListeners();
}
void switchLanguage(bool state, BuildContext context) async {
state == true
? _switchState = SwitchState.OPEN
: _switchState = SwitchState.CLOSE;
notifyListeners();
if (context.locale == const Locale('ar', 'EG')) {
context.setLocale(const Locale('en', 'US'));
} else {
context.setLocale(const Locale('ar', 'EG'));
}
}
}
and this is the below Drawer component:
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:l7/screens/BaseScreen.dart';
import 'package:l7/screens/main/view_model/main_view_model.dart';
import 'package:l7/utils/constants.dart';
class SideMenu extends StatelessWidget {
// final MenuController _controller = Get.put(MenuController());
#override
Widget build(BuildContext context) {
return BaseScreen<MainViewModel>(
onModelReady: (mainViewModel){},
builder: (context, viewModel, _){
return Drawer(
child: Container(
color: kDarkBlackColor,
child: ListView(
children: [
DrawerHeader(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: kDefaultPadding * 3.5),
child: SvgPicture.asset("assets/icons/logo.svg"),
),
),
...List.generate(
viewModel.menuItems.length,
(index) => DrawerItem(
isActive: index == viewModel.selectedIndex,
title: viewModel.menuItems[index],
press: () {
viewModel.setMenuIndex(index);
},
),
),
],
),
),
);
},
);
}
}
class DrawerItem extends StatelessWidget {
final String? title;
final bool? isActive;
final VoidCallback? press;
const DrawerItem({
Key? key,
#required this.title,
#required this.isActive,
#required this.press,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return SafeArea(
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: kDefaultPadding),
selected: isActive!,
selectedTileColor: kPrimaryColor,
onTap: press,
title: Text(
title!,
style: TextStyle(color: Colors.white),
),
),
);
}
}
and this is the Header Widget which contains the drawer:
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:l7/screens/BaseScreen.dart';
import 'package:l7/screens/main/view/components/web_menu.dart';
import 'package:l7/screens/main/view_model/main_view_model.dart';
import 'package:l7/services/responsive.dart';
import 'package:l7/utils/constants.dart';
import 'package:l7/utils/texts.dart';
import 'header_right_side.dart';
class Header extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BaseScreen<MainViewModel>(
onModelReady: (mainViewModel) {},
builder: (context, viewModel, _) {
return SafeArea(
child: Column(
children: [
Column(
children: [
Row(
children: [
if (!Responsive.isDesktop(context))
IconButton(
icon: Icon(
Icons.menu,
color: kBlackBlue,
),
onPressed: () {
viewModel.openOrCloseDrawer();
},
),
Image.asset("assets/images/l7_image.png", height: MediaQuery.of(context).size.height * 0.15,),
Spacer(),
if (Responsive.isDesktop(context)) WebMenu(),
Spacer(),
HeaderRightSide(),
],
),
// SizedBox(height: kDefaultPadding * 2),
Container(
width: double.infinity,
height: MediaQuery.of(context).size.height * 0.27,
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage('assets/images/blog_bg.png'), fit: BoxFit.cover)
),
child: Row(
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
headLine30TitleText(
tr("Welcome to Our Blog"),
context,
),
Padding(
padding:
const EdgeInsets.symmetric(vertical: kDefaultPadding),
child: Text(
"Stay updated with the newest design and development stories, case studies, \nand insights shared by DesignDK Team.",
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontFamily: 'Raleway',
height: 1.5,
),
),
),
FittedBox(
child: TextButton(
onPressed: () {},
child: Row(
children: [
Text(
"Learn More",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(width: kDefaultPadding / 2),
Icon(
Icons.arrow_forward,
color: Colors.white,
),
],
),
),
),
],
),
),
IconButton(onPressed: (){}, icon: Icon(Icons.keyboard_arrow_right, color: Colors.white,))
],
),
),
if (Responsive.isDesktop(context))
SizedBox(height: kDefaultPadding),
],
)
],
),
);
},
);
}
}
I hope someone could help, and let me know if there's any missing details or code. :)
The solution in the below line code in Header widget:
instead of:
viewModel.openOrCloseDrawer();
add this line:
Scaffold.of(context).openDrawer();

Flutter : The onRefresh callback returned null

Sorry I'm new to flutter and trying to learn it. I have an issue with RefreshIndicator.
When I try pulling it I got an error like below :
════════ Exception caught by material library ══════════════════════════════════════════════════════
The following assertion was thrown when calling onRefresh:
The onRefresh callback returned null.
The RefreshIndicator onRefresh callback must return a Future.
════════════════════════════════════════════════════════════════════════════════════════════════════
Currently I am using flutter_bloc. Here is my sample code
TableList_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:pos_project_bloc/modal/api.dart';
class ModalTableList {
final String Table_Number;
final String Name_Table;
ModalTableList(
this.Table_Number,
this.Name_Table,
);
}
class tablelistbloc extends Bloc<bool, List<ModalTableList>>{
#override
// TODO: implement initialState
List<ModalTableList> get initialState => [];
#override
Stream<List<ModalTableList>> mapEventToState(bool event) async* {
// TODO: implement mapEventToState
List<ModalTableList> tablelist =[];
try {
final response = await http.get(BaseUrl.GetTableList);
final data = jsonDecode(response.body);
if (data.length != 0) {
data.forEach((api) {
tablelist.add(
ModalTableList(
api['Table_Number'],
api['Name_Table'],
)
);
print("test"+api['Table_Number'].toString());
});
print("Get Table List : sukses");
} else {
print('data kosong');
}
} catch (e) {
print("Error GetTableList :");
print(e);
}
yield tablelist;
}
}
TabeList.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pos_project_bloc/bloc/TableList_bloc.dart';
import 'package:pos_project_bloc/modal/SharedPreferences.dart';
class TableList extends StatefulWidget {
#override
_TableListState createState() => _TableListState();
}
class _TableListState extends State<TableList> {
#override
void initState() {
super.initState();
BlocProvider.of<tablelistbloc>(context).add(true);
}
Widget build(BuildContext context) {
final GlobalKey<RefreshIndicatorState> refresh = GlobalKey<RefreshIndicatorState>();
Future<Null> _refresh() async{
BlocProvider.of<tablelistbloc>(context).add(true);
}
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.lightBlueAccent,
title: new Center(
child: new Text('Available Table',
style: new TextStyle(color: Colors.white, fontSize: 15.0)),
)
),
floatingActionButton: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: EdgeInsets.all(5.0),
child: FloatingActionButton.extended(
heroTag: 1,
icon: Icon(Icons.refresh),
label: Text('Refresh Table List'),
onPressed: () {
BlocProvider.of<tablelistbloc>(context).add(true);
},
),
)
],
),
),
body: RefreshIndicator(
onRefresh: (){
_refresh();
},
key: refresh,
child: BlocBuilder<tablelistbloc,List<ModalTableList>>(
builder: (context,tablelist)=> ListView.builder(
itemCount: tablelist.length,
itemBuilder: (context,index){
final x = tablelist[index];
return GestureDetector(
onTap: (){
print("Selected Available Table On Tap : " + x.Table_Number);
Shared_Preferences().SaveTableNumber(
x.Table_Number
);
Navigator.pushReplacementNamed(context, '/POS');
},
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 2.0,
color: Colors.grey.withOpacity(0.4),
))),
padding: EdgeInsets.all(10.0),
child: Row(
children: <Widget>[
Icon(
Icons.table_chart,
size: 100.0,
color: Colors.lightBlueAccent,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Table Number : ' + x.Table_Number,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.lightBlueAccent),
),
Text(
'Name : ' + x.Name_Table,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.lightBlueAccent),
),
],
)
],
),
),
);
},
),
),
)
);
}
}
Just add async.
onRefresh: () async {
GetTableList.add(true);
},
onRefresh is a RefreshCallback, and RefreshCallback is a Future Function().
So if GetTableList.add(true) not return Future, must to add async.
it is says onRefresh callback must return a Future.
and it is seems like your are not returning Future from onRefresh
onRefresh: _refresh
Future<Null> _refresh() async{
GetTableList.add(true);
}
hope it helps..
You can problably use the following:
Future<bool> refresh() async {
//your refresh code
return true;
}

How to pass data back from a widget?

I have a screen where users can add a location. Here, I have separated all my widgets into there own files as illustrated below;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:fluttershare/pages/location/location_help_screen.dart';
import 'package:fluttershare/widgets/common_widgets/customDivider.dart';
import 'package:uuid/uuid.dart';
import '../../widgets/camp_type_select.dart';
import '../../widgets/extra_location_notes.dart';
import '../../widgets/location_input.dart';
import '../../widgets/opening_times.dart';
import '../../widgets/post_media.dart';
import '../../widgets/space_avalibility.dart';
import '../../widgets/utility_type_select.dart';
import '../../widgets/width_restriction.dart';
import '../../widgets/height_restriction.dart';
import '../../models/locations.dart';
import '../../models/user.dart';
import '../home.dart';
class AddNewLocation extends StatefulWidget {
static const routeName = '/add-new-location';
final User currentUser;
AddNewLocation({this.currentUser});
_AddNewLocationState createState() => _AddNewLocationState();
}
class _AddNewLocationState extends State<AddNewLocation> {
String postId = Uuid().v4();
final _scaffoldKey = GlobalKey<ScaffoldState>();
PlaceLocation _pickedLocation;
int storyPostCount = 0;
bool isLoading = false;
void _selectPlace(double lat, double lng) {
_pickedLocation = PlaceLocation(lattitude: lat, longitude: lng);
}
getLocationPostCount() async {
setState(() {
isLoading = true;
});
QuerySnapshot snapshot = await locationPostRef
.document(currentUser.id)
.collection('user_location_posts')
.getDocuments();
setState(() {
storyPostCount = snapshot.documents.length;
});
}
createLocationPostInFirestore(
{String mediaUrl,
String description,
double heightRestriction,
double widthRestriction}) {
locationPostRef
.document(currentUser.id)
.collection("user_location_posts")
.document(postId)
.setData({
"postId": postId,
"ownerId": currentUser.id,
"username": currentUser.username,
"description": description,
"timestamp": timestamp,
"lattitude": _pickedLocation.lattitude,
"longitude": _pickedLocation.longitude,
"max_height": heightRestrictionValue.toStringAsFixed(0),
"max_width": widthRestrictionValue.toStringAsFixed(0),
});
}
handlePostSubmit() {
createLocationPostInFirestore(
heightRestriction: heightRestrictionValue,
widthRestriction: widthRestrictionValue,
);
SnackBar snackbar = SnackBar(
content: Text("Profile Updated"),
);
_scaffoldKey.currentState.showSnackBar(snackbar);
setState(() {
postId = Uuid().v4();
});
}
buildUploadUserHeader() {
return Container(
margin: EdgeInsets.only(bottom: 10),
height: 200,
child: Row(
children: <Widget>[
Expanded(
flex: 2,
child: Container(
color: Colors.blue,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ListTile(
leading: CircleAvatar(
backgroundImage:
CachedNetworkImageProvider(currentUser.photoUrl)),
),
],
),
),
),
Expanded(
flex: 6,
child: Container(
color: Colors.pink,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text(currentUser.displayName),
],
),
),
),
],
),
);
}
buildCampUploadForm() {
return Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
//buildUploadUserHeader(), //TODO: This is the profile header that is dissabled for now. Work on possibly a header in the future.
Container(
padding: EdgeInsets.all(15),
child: Column(
children: <Widget>[
CampTypeSelect(),
CustomDivider(),
LocationInput(_selectPlace),
CustomDivider(),
HeightRestriction(),
WidthRestriction(),
SpaceAvalibility(),
OpeningTimes(),
CustomDivider(),
PostMedia(),
CustomDivider(),
UtilityServices(),
CustomDivider(),
ExtraLocationNotes(),
Container(
height: 80,
margin: EdgeInsets.only(top: 10, bottom: 10),
child: Row(
children: <Widget>[
Expanded(
child: FlatButton(
color: Colors.black,
onPressed: () => handlePostSubmit(),
child: Text(
"SUBMIT",
style: Theme.of(context).textTheme.display2,
),
padding: EdgeInsets.all(20),
),
)
],
),
),
],
),
),
],
),
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
automaticallyImplyLeading: false,
title: const Text(
'Add New Location',
style: TextStyle(color: Colors.black),
),
actions: <Widget>[
// action button
IconButton(
icon: Icon(Icons.info_outline),
color: Colors.black,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) => LocationSubmitHelpScreen()),
);
},
),
// action button
IconButton(
icon: Icon(Icons.close),
color: Colors.black,
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
body: buildCampUploadForm(),
backgroundColor: Colors.white,
);
}
}
What I am trying to do is pass the data back from the widget ExtraLocationNotes()
to the function createLocationPostInFirestore().
For context, this is what my widget looks like;
import 'package:flutter/material.dart';
import 'common_widgets/custom_form_card.dart';
class ExtraLocationNotes extends StatefulWidget {
_ExtraLocationNotesState createState() => _ExtraLocationNotesState();
}
class _ExtraLocationNotesState extends State<ExtraLocationNotes> {
TextEditingController descriptionController = TextEditingController();
#override
Widget build(BuildContext context) {
return CustomFormCard(
child: Column(
children: <Widget>[
Container(
child: Row(
children: <Widget>[
Text(
"EXTRA INFORMATION",
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
fontWeight: FontWeight.w400,
letterSpacing: 2.0,
),
),
],
),
),
SizedBox(height: 20),
TextFormField(
controller: descriptionController,
maxLines: 6,
maxLength: 250,
maxLengthEnforced: true,
style:
new TextStyle(fontSize: 18.0, height: 1.3, color: Colors.black),
decoration: const InputDecoration(
hintText:
"Please write a description of this location for fellow travellers.",
alignLabelWithHint: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.only(),
borderSide: BorderSide(color: Colors.black),
),
),
),
],
),
);
}
}
How do I pass the data back to the parent widget?
You need a callback, which will be triggered in the child widget then the value will be updated in the parent widget:
// 1- Define a pointers to executable code in memory, which is the callback.
typedef void MyCallback(String val);
class ExtraLocationNotes extends StatefulWidget {
// 2- You will pass it to this widget with the constructor.
final MyCallback cb;
// 3- ..pass it to this widget with the constructor
ExtraLocationNotes({this.cb});
_ExtraLocationNotesState createState() => _ExtraLocationNotesState();
}
class _ExtraLocationNotesState extends State<ExtraLocationNotes> {
//..
//...
RaisedButton(
//..
// 4- in any event inside the child you can call the callback with
// the data you want to send back to the parent widget:
onPressed: () {
widget.cb("Hello from the other side!");
}
),
}
Then inside the parent widget you need to catch the data which sent form the child:
class AddNewLocation extends StatefulWidget {
//...
_AddNewLocationState createState() => _AddNewLocationState();
}
class _AddNewLocationState extends State<AddNewLocation> {
// 1- Global var to store the data that we're waiting for.
String _dataFromMyChild = "";
buildCampUploadForm() {
return Container(
//...
//...
// 2- Pass the callback with the constructor of the child, this
// will update _dataFromMyChild's value:
ExtraLocationNotes(cb: (v) => setState(() => _dataFromMyChild = v)),
//..
}
// then
createLocationPostInFirestore() {
// Use _dataFromMyChild's value here
}
}
You can use the BuildContext object to get the context widget (might no be the parent!) couldn't read it all but as i understand that you need to pass the info from the child to the parent ,and you can do it with some like this :-
(context.widget as MyType).doStuff();
Note.
please check first with
print(context.widget.runtimeType);
but to make a better solution make a mutable data object that is passed from parent to the child so when changes happens it reflect's on the parent so you can separate business logic from ui logic.