Using ChangeNotifier to Update Home Widget with Button Press in Flutter - flutter

Here is the substance of my main.dart file:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:webview_flutter/webview_flutter.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Home()),
ChangeNotifierProvider(create: (_) => _HomeState()),
ChangeNotifierProvider(create: (_) => _SettingsPageState()),
],
child: MaterialApp(
title: 'My Great App',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: Home(),
),
),
);
}
class BottomNavBarItemData {
String label;
IconData icon;
BottomNavBarItemData(this.label, this.icon);
}
class PageViewData {
Widget page;
PageViewData(this.page);
}
//The Main Page Class
class Home extends StatefulWidget with ChangeNotifier {
Home({super.key});
int _selectedIdx = 0;
int get selectedIdx => _selectedIdx;
late Widget _body;
void updateSelectedIndex(index) {
print('This is _selectedIdx:');
print(_selectedIdx);
_selectedIdx = index;
print('This is now _selectedIdx:');
print(_selectedIdx);
print('This is selectedIdx:');
print(selectedIdx);
notifyListeners();
}
final List<BottomNavBarItemData> navIcons = [
BottomNavBarItemData("Home", Icons.cottage),
BottomNavBarItemData("Settings", Icons.settings),
];
final List<PageViewData> pageScreens = [
PageViewData(const HomePage()),
PageViewData(SettingsPage()),
PageViewData(const SettingsPage2()),
];
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with ChangeNotifier {
final pageTitles = ['My Home', 'My Settings', 'My Settings 2'];
int get thisSelectedIdx => widget._selectedIdx;
#override
void initState() {
print('Init State for Home');
super.initState();
}
#override
Widget build(BuildContext context) {
print('This is the top thisSelectedIdx:');
print(thisSelectedIdx);
if (thisSelectedIdx == 2) {
print('The index is now 2!');
widget._body = IndexedStack(
index: widget._selectedIdx,
children: const [
SettingsPage2(),
],
);
} else {
widget._body = IndexedStack(
index: widget._selectedIdx,
children: [
...widget.pageScreens.map((e) => e.page).toList(),
],
);
}
return Scaffold(
appBar: TopAppBar(appBarTitle: pageTitles[widget._selectedIdx]),
body: widget._body,
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: widget._selectedIdx,
onTap: (idx) => setState(() {
widget._selectedIdx = idx;
Home().updateSelectedIndex(widget._selectedIdx);
print('This is widget._selectedIdx:');
print(widget._selectedIdx);
print('This is thisSelectedIdx:');
print(thisSelectedIdx);
}),
items: widget.navIcons
.map(
(e) => BottomNavigationBarItem(
label: e.label,
icon: Icon(e.icon),
),
)
.toList(),
showSelectedLabels: true,
showUnselectedLabels: true,
backgroundColor: Colors.black,
selectedItemColor: const Color.fromARGB(255, 0, 255, 0),
unselectedItemColor: Colors.grey,
),
);
}
}
//The Reload Button Widget
class ReloadButton extends StatelessWidget {
const ReloadButton({super.key});
#override
Widget build(BuildContext context) {
return IconButton(
color: const Color.fromARGB(255, 0, 255, 0),
icon: const Icon(Icons.refresh),
tooltip: 'Refresh the Page',
onPressed: () {
print('Page Refreshed');
},
);
}
}
//The App Bar Class
class TopAppBar extends StatefulWidget implements PreferredSizeWidget {
const TopAppBar(
{super.key,
required this.appBarTitle,
this.preferredSize = const Size.fromHeight(50)});
final String appBarTitle;
#override
final Size preferredSize;
#override
State<TopAppBar> createState() => _TopAppBarState();
}
class _TopAppBarState extends State<TopAppBar> {
#override
void initState() {
print('Init State for TopAppBar');
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return AppBar(
foregroundColor: const Color.fromARGB(255, 0, 255, 0),
title: Text(widget.appBarTitle),
actions: const <Widget>[
ReloadButton(), //IconButton
], //<Widget>[]
backgroundColor: Colors.black,
automaticallyImplyLeading: false,
);
}
}
//The Home Page Class
class HomePage extends StatefulWidget {
const HomePage({super.key});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late final _controller = WebViewController();
int indexPosition = 0;
beginLoading(String A) {
if (!mounted) return;
setState(() {
indexPosition = 1;
});
}
completeLoading(String A) {
if (!mounted) return;
setState(() {
indexPosition = 0;
});
}
#override
void initState() {
print('Init State for Home Page');
_controller
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..clearCache()
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (String url) {
beginLoading(url);
},
onPageFinished: (String url) {
completeLoading(url);
},
onWebResourceError: (WebResourceError error) {},
),
)
..loadRequest(Uri.parse('https://www.flutter.dev'));
super.initState();
}
#override
void dispose() {
super.dispose();
_controller;
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.black,
padding: const EdgeInsets.only(left: 5.0, right: 5.0),
child: IndexedStack(
index: indexPosition,
children: <Widget>[
WebViewWidget(controller: _controller),
Container(
color: Colors.black,
child: const Center(child: CircularProgressIndicator()),
),
],
),
);
}
}
//The Settings Page Class
class SettingsPage extends StatefulWidget with ChangeNotifier {
SettingsPage({super.key});
#override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> with ChangeNotifier {
bool _thisOptionIsChecked = false;
bool _thisOption = false;
String _thisOptionText = 'Off';
#override
void initState() {
print('Init State for Settings Page');
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
Color getColor(Set<MaterialState> states) {
const Set<MaterialState> interactiveStates = <MaterialState>{
MaterialState.selected,
MaterialState.focused,
MaterialState.pressed,
};
if (states.any(interactiveStates.contains)) {
return Colors.green;
}
return Colors.red;
}
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Center(
child: Container(
color: Colors.black,
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
width: 180,
child: Column(
children: const <Widget>[
Text(
'This Option',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
],
),
),
SizedBox(
width: 50,
child: Column(
children: <Widget>[
Checkbox(
checkColor: Colors.white,
fillColor:
MaterialStateProperty.resolveWith(getColor),
value: _thisOptionIsChecked,
onChanged: (bool? value) {
if (!mounted) return;
setState(() {
_thisOptionIsChecked = value!;
_thisOption = value;
if (_thisOption == true) {
_thisOptionText = 'On';
} else {
_thisOptionText = 'Off';
}
});
},
),
],
),
),
SizedBox(
width: 75,
child: Column(
children: <Widget>[
Text(
_thisOptionText,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
],
),
),
SizedBox(
width: 50,
child: Column(
children: const <Widget>[
Text(''),
],
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 35,
width: 120,
child: TextButton(
style: TextButton.styleFrom(
textStyle: const TextStyle(
fontSize: 11, fontWeight: FontWeight.bold),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0)),
backgroundColor: Colors.grey,
foregroundColor: Colors.black,
),
onPressed: () {
int index = 7;
Provider.of<Home>(context, listen: false)
.updateSelectedIndex(index);
notifyListeners();
print('Open Location Settings');
},
child: const Text('My Settings 2'),
),
),
],
),
],
),
),
);
});
}
}
class SettingsPage2 extends StatefulWidget {
const SettingsPage2({super.key});
#override
State<SettingsPage2> createState() => _SettingsPage2State();
}
class _SettingsPage2State extends State<SettingsPage2> {
late var _controller = WebViewController();
int indexPosition = 0;
beginLoading(String A) {
if (!mounted) return;
setState(() {
indexPosition = 1;
});
}
completeLoading(String A) {
if (!mounted) return;
setState(() {
indexPosition = 0;
});
}
#override
void initState() {
print('Init State for My Settings Page 2');
super.initState();
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (String url) {
beginLoading(url);
},
onPageFinished: (String url) {
completeLoading(url);
},
onWebResourceError: (WebResourceError error) {},
),
)
..loadRequest(Uri.parse('https://www.google.com'));
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.black,
padding: const EdgeInsets.only(left: 5.0, right: 5.0),
child: IndexedStack(
index: indexPosition,
children: <Widget>[
WebViewWidget(
controller: _controller,
),
Container(
color: Colors.black,
child: const Center(child: CircularProgressIndicator()),
),
],
),
);
}
}
I only have the two pages, Home and Settings, loading in my navigation menu because I want the second page of settings to remain hidden until the user taps on Settings and then presses the My Settings 2 button.
I am a bit lost using the ChageNotifier provider, which I think is what I need, and I am not sure what else to try. The main page loads with a web page and works great. The Settings page opens and works great as well. On that page, I have a button which, when pressed, should open up another page in the main window. I am using the selectedIdx and _selectedIdx variables to track movement between the pages. When I click on Home or Settings in the bottomNavigationBar, everything prints appropriately. I get the printed output I would expect:
flutter: This is _selectedIdx:
flutter: 0
flutter: This is now _selectedIdx:
flutter: 0
flutter: This is selectedIdx:
flutter: 0
flutter: This is widget._selectedIdx:
flutter: 0
flutter: This is thisSelectedIdx:
flutter: 0
flutter: This is the top thisSelectedIdx:
flutter: 0
flutter: This is _selectedIdx:
flutter: 0
flutter: This is now _selectedIdx:
flutter: 1
flutter: This is selectedIdx:
flutter: 1
flutter: This is widget._selectedIdx:
flutter: 1
flutter: This is thisSelectedIdx:
flutter: 1
flutter: This is the top thisSelectedIdx:
flutter: 1
However, when I click on the My Settings 2 button, it is not behaving the way I would expect it to. It does call the Provider.of<Home>(context, listen: false).updateSelectedIndex(index) function and passed the index of 2, but it just dies there. I get the following:
flutter: This is _selectedIdx:
flutter: 0
flutter: This is now _selectedIdx:
flutter: 2
flutter: This is selectedIdx:
flutter: 2
flutter: Open Settings Page 2
What I would expect to see is this:
flutter: This is _selectedIdx:
flutter: 0
flutter: This is now _selectedIdx:
flutter: 2
flutter: This is selectedIdx:
flutter: 2
flutter: This is widget._selectedIdx:
flutter: 2
flutter: This is thisSelectedIdx:
flutter: 2
flutter: This is the top thisSelectedIdx:
flutter: 2
flutter: Open Settings Page 2
This tells me that the Home class is not being rebuilt. So, even though the function runs, I never get an updated widget for the Home class. I am racking my brain on how to get this button to show the SettingsPage2 class. Any ideas to help get me unstuck? I thought this would be fairly simple to do, but it just doesn't work the way my mind thinks it should.

Related

showMenu flutter: want to show items by horizontal

i want to create a menu popup horizontal when long press the icon category. i using showMenu function in flutter. but it's displayed in vertical
this is my code:
this is design i want:
I also attach my code
GestureDetector(
onTapDown: _storePosition,
onLongPress: () {
final RenderBox overlay = Overlay.of(context)!
.context
.findRenderObject() as RenderBox;
showMenu(
context: context,
position: RelativeRect.fromRect(
controller.tapPosition & const Size(40, 40),
Offset.zero & overlay.size,
),
items: [
PopupMenuItem<String>(
value: '1',
child: _category(
'assets/icons/category_apartment.png'),
),
PopupMenuItem<String>(
value: '2',
child: _category('assets/icons/category_beer.png'),
),
],
elevation: 8.0,
).then<void>(
(String? itemSelected) async {
print('itemSelected $itemSelected');
},
);
},
child: RepaintBoundary(
key: controller.globalKey,
child: Image.asset('assets/icons/icon_category.png',
height: 25.0),
),
),
You may achieve this using Overlay
Run the code below and adapt it with your code.
It creates an Overlay then adds it next to the item clicked by using context.findRenderObject() to find coordinates. Then the overlay can be removed using Overlay.remove()
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ProfilePage(),
),
),
);
}
}
class ProfilePage extends StatelessWidget {
const ProfilePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
'Profile Page',
),
),
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
const CountryFormField(),
Text("Som Text"),
Text("Som Text"),
],
),
Text("Som Text"),
Text("Som Text"),
Text("Som Text"),
],
),
);
}
}
class CountryFormField extends StatefulWidget {
const CountryFormField({Key? key}) : super(key: key);
#override
_CountryFormFieldState createState() => _CountryFormFieldState();
}
class _CountryFormFieldState extends State<CountryFormField>
with TickerProviderStateMixin {
// focus node object to detect gained or loss on textField
final FocusNode _focusNode = FocusNode();
OverlayEntry? _overlayEntry;
GlobalKey globalKey = GlobalKey();
final LayerLink _layerLink = LayerLink();
OverlayState? overlayState;
#override
void initState() {
super.initState();
overlayState = Overlay.of(context);
WidgetsBinding.instance!.addPostFrameCallback((_) {
globalKey;
});
}
OverlayEntry _createOverlay() {
RenderBox renderBox = context.findRenderObject() as RenderBox;
var size = renderBox.size;
Offset position = renderBox.localToGlobal(Offset.zero);
return OverlayEntry(
builder: (context) => Positioned(
top: position.dy + size.height / 2,
left: size.width,
child: Material(
elevation: 5.0,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () {
_overlayEntry?.remove();
_overlayEntry = null;
},
child: Icon(Icons.refresh),
),
Icon(Icons.done),
Icon(Icons.close),
Icon(Icons.search),
],
),
),
));
}
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
if (_overlayEntry != null) return;
_overlayEntry = _createOverlay();
overlayState!.insert(_overlayEntry!);
},
child: const Text('SUBMIT'));
}
}

Flutter State Management with Bloc/Cubit

for many of you this is an obvious / stupid question, but I've come to a point where I don't have a clue anymore. I have real difficulties understanding State Management with Bloc / Cubit.
Expectation: I have a page with a ListView (recipe_list) of all recipes and an 'add' button. Whenever I click on a ListItem or the 'add' button I go to the next page (recipe_detail). On this page I can create a new recipe (if clicked the 'add' button before), update or delete the existing recipe (if clicked on ListItem before). When I click the 'save' or 'delete' button the Navigator pops and I go back to the previous page (recipe_list). I used Cubit to manage the state of the recipe list. My expectation is that the ListView updates automatically after I clicked 'save' or 'delete'. But I have to refresh the App to display the changes.
main.dart
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Recipe Demo',
home: BlocProvider<RecipeCubit>(
create: (context) => RecipeCubit(RecipeRepository())..getAllRecipes(),
child: const RecipeList(),
)
);
}
}
recipe_list.dart
class RecipeList extends StatefulWidget {
const RecipeList({Key? key}) : super(key: key);
#override
_RecipeListState createState() => _RecipeListState();
}
class _RecipeListState extends State<RecipeList> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 24.0
),
color: const Color(0xFFF6F6F6),
child: Stack(
children: [
Column(
children: [
Container(
margin: const EdgeInsets.only(
top: 32.0,
bottom: 32.0
),
child: const Center(
child: Text('Recipes'),
),
),
Expanded(
child: BlocBuilder<RecipeCubit, RecipeState>(
builder: (context, state) {
if (state is RecipeLoading) {
return const Center(
child: CircularProgressIndicator(),
);
} else if (state is RecipeError) {
return const Center(
child: Icon(Icons.close),
);
} else if (state is RecipeLoaded) {
final recipes = state.recipes;
return ListView.builder(
itemCount: recipes.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return BlocProvider<RecipeCubit>(
create: (context) => RecipeCubit(RecipeRepository()),
child: RecipeDetail(recipe: recipes[index]),
);
}
));
},
child: RecipeCardWidget(
title: recipes[index].title,
description: recipes[index].description,
),
);
},
);
} else {
return const Text('Loading recipes error');
}
}
),
),
],
),
Positioned(
bottom: 24.0,
right: 0.0,
child: FloatingActionButton(
heroTag: 'addBtn',
onPressed: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return BlocProvider<RecipeCubit>(
create: (context) => RecipeCubit(RecipeRepository()),
child: const RecipeDetail(recipe: null),
);
}
));
},
child: const Icon(Icons.add_rounded),
backgroundColor: Colors.teal,
),
)
],
),
),
),
);
}
}
recipe_detail.dart
class RecipeDetail extends StatefulWidget {
final Recipe? recipe;
const RecipeDetail({Key? key, required this.recipe}) : super(key: key);
#override
_RecipeDetailState createState() => _RecipeDetailState();
}
class _RecipeDetailState extends State<RecipeDetail> {
final RecipeRepository recipeRepository = RecipeRepository();
final int _recipeId = 0;
late String _recipeTitle = '';
late String _recipeDescription = '';
final recipeTitleController = TextEditingController();
final recipeDescriptionController = TextEditingController();
late FocusNode _titleFocus;
late FocusNode _descriptionFocus;
bool _buttonVisible = false;
#override
void initState() {
if (widget.recipe != null) {
_recipeTitle = widget.recipe!.title;
_recipeDescription = widget.recipe!.description;
_buttonVisible = true;
}
_titleFocus = FocusNode();
_descriptionFocus = FocusNode();
super.initState();
}
#override
void dispose() {
recipeTitleController.dispose();
recipeDescriptionController.dispose();
_titleFocus.dispose();
_descriptionFocus.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 24.0
),
color: const Color(0xFFF6F6F6),
child: Stack(
children: [
Column(
children: [
Align(
alignment: Alignment.topLeft,
child: InkWell(
child: IconButton(
highlightColor: Colors.transparent,
color: Colors.black54,
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back_ios_new_rounded),
),
),
),
TextField(
focusNode: _titleFocus,
controller: recipeTitleController..text = _recipeTitle,
decoration: const InputDecoration(
hintText: 'Enter recipe title',
border: InputBorder.none
),
style: const TextStyle(
fontSize: 26.0,
fontWeight: FontWeight.bold
),
onSubmitted: (value) => _descriptionFocus.requestFocus(),
),
TextField(
focusNode: _descriptionFocus,
controller: recipeDescriptionController..text = _recipeDescription,
decoration: const InputDecoration(
hintText: 'Enter recipe description',
border: InputBorder.none
),
),
],
),
Positioned(
bottom: 24.0,
left: 0.0,
child: FloatingActionButton(
heroTag: 'saveBtn',
onPressed: () {
if (widget.recipe == null) {
Recipe _newRecipe = Recipe(
_recipeId,
recipeTitleController.text,
recipeDescriptionController.text
);
context.read<RecipeCubit>().createRecipe(_newRecipe);
//recipeRepository.createRecipe(_newRecipe);
Navigator.pop(context);
} else {
Recipe _newRecipe = Recipe(
widget.recipe!.id,
recipeTitleController.text,
recipeDescriptionController.text
);
context.read<RecipeCubit>().updateRecipe(_newRecipe);
//recipeRepository.updateRecipe(_newRecipe);
Navigator.pop(context);
}
},
child: const Icon(Icons.save_outlined),
backgroundColor: Colors.amberAccent,
),
),
Positioned(
bottom: 24.0,
right: 0.0,
child: Visibility(
visible: _buttonVisible,
child: FloatingActionButton(
heroTag: 'deleteBtn',
onPressed: () {
context.read<RecipeCubit>().deleteRecipe(widget.recipe!.id!);
//recipeRepository.deleteRecipe(widget.recipe!.id!);
Navigator.pop(context);
},
child: const Icon(Icons.delete_outline_rounded),
backgroundColor: Colors.redAccent,
),
),
),
],
),
),
),
);
}
}
recipe_state.dart
part of 'recipe_cubit.dart';
abstract class RecipeState extends Equatable {
const RecipeState();
}
class RecipeInitial extends RecipeState {
#override
List<Object> get props => [];
}
class RecipeLoading extends RecipeState {
#override
List<Object> get props => [];
}
class RecipeLoaded extends RecipeState {
final List<Recipe> recipes;
const RecipeLoaded(this.recipes);
#override
List<Object> get props => [recipes];
}
class RecipeError extends RecipeState {
final String message;
const RecipeError(this.message);
#override
List<Object> get props => [message];
}
recipe_cubit.dart
part 'recipe_state.dart';
class RecipeCubit extends Cubit<RecipeState> {
final RecipeRepository recipeRepository;
RecipeCubit(this.recipeRepository) : super(RecipeInitial()) {
getAllRecipes();
}
void getAllRecipes() async {
try {
emit(RecipeLoading());
final recipes = await recipeRepository.getAllRecipes();
emit(RecipeLoaded(recipes));
} catch (e) {
emit(const RecipeError('Error'));
}
}
void createRecipe(Recipe recipe) async {
await recipeRepository.createRecipe(recipe);
final newRecipes = await recipeRepository.getAllRecipes();
emit(RecipeLoaded(newRecipes));
}
void updateRecipe(Recipe recipe) async {
await recipeRepository.updateRecipe(recipe);
final newRecipes = await recipeRepository.getAllRecipes();
emit(RecipeLoaded(newRecipes));
}
void deleteRecipe(int id) async {
await recipeRepository.deleteRecipe(id);
final newRecipes = await recipeRepository.getAllRecipes();
emit(RecipeLoaded(newRecipes));
}
}
It looks like you're creating another BlocProvider when you're navigating to RecipeDetail page. When you're pushing new MaterialPageRoute, this new page gets additionally wrapped in new RecipeCubit. Then, when you're calling context.read<RecipeCubit>(), you're referencing that provider (as this is closest BlocProvider in the widget tree). Your RecipeList can't react to those changes because it's BlocBuilder is looking for a BlocProvider declared above it in the widget tree (the one in MyApp).
Besides that, newly created provider gets removed from the widget tree anyway when you're closing RecipeDetail page as it is declared in the MaterialPageRoute which has just been pushed off the screen.
Try to remove the additional BlocProvider (the one in RecipeList, in OnTap function of RecipeCardWidget):
onTap: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return BlocProvider<RecipeCubit>( // remove this BlocProvider
create: (context) => RecipeCubit(RecipeRepository()),
child: RecipeDetail(recipe: recipes[index]),
);
}
));
},

Flutter How to set Boolean from Settingpage

How to turn On/Off vibration on Homepage from Settings page with boolean SwitchListTile?
I want if the SwitchListTile in the Settings page is On, the Homepage will vibrate every time I tap it, and vice versa. basically I don't know how to control certain pages from other pages
this is MySettingPage
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class MySettingPage extends StatefulWidget {
const MySettingPage({Key key}) : super(key: key);
#override
_MySettingPageState createState() => _MySettingPageState();
}
class _MySettingPageState extends State<MySettingPage> {
bool isVibrate = false;
#override
void initState() {
super.initState();
getSwitchValues();
}
getSwitchValues() async {
isVibrate = await getSwitchState();
setState(() {});
}
Future<bool> saveSwitchState(bool value) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool("switchState", value);
return prefs.setBool("switchState", value);
}
Future<bool> getSwitchState() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool isVibrate = prefs.getBool("switchState");
return isVibrate;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
titleSpacing: 0,
title: Text("Pengaturan"),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.of(context).pop();
}),
),
body: Container(
padding: EdgeInsets.all(10),
child: ListView(
children: [
SwitchListTile(
title: Text("Getar"),
value: isVibrate,
onChanged: (bool value) async {
setState(() {
isVibrate = value;
saveSwitchState(value);
});
},
),
//
],
),
),
);
}
}
this is MyHomePage
import 'package:flutter/material.dart';
import 'package:vibration/vibration.dart';
import 'mysettingpage.dart';
class MyHomePage extends StatefulWidget {
final bool isVibrate;
MyHomePage({Key key, this.isVibrate}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
if (widget.isVibrate == true) {
Vibration.vibrate(duration: 70);
}
if (widget.isVibrate == false) {
Vibration.cancel();
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("My Homepage"),
titleSpacing: 0,
leading: IconButton(
icon: Icon(Icons.settings),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => MySettingPage(),
));
},
),
),
body: GestureDetector(
onTap: () {
_incrementCounter();
},
child: Container(
height: double.infinity,
width: double.infinity,
child: Padding(
padding: const EdgeInsets.only(bottom: 120),
child: Column(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: FittedBox(
child: Text(
'$_counter',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 200,
fontFamily: 'DS-Digital',
color: Color(0xFF24F3E2),
),
),
),
),
],
),
),
],
),
),
),
),
);
}
}
To continue on the response from Allan C with the changes to your code: (untested)
HomePage:
import 'package:flutter/material.dart';
import 'package:vibration/vibration.dart';
import 'mysettingpage.dart';
class MyHomePage extends StatefulWidget {
final bool isVibrate;
MyHomePage({Key key, this.isVibrate}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
bool _isVibrate;
#override
void initState() {
super.initState();
_isVibrate = widget.isVibrate;
}
void _onVibrateChange(bool value) {
setState(() {
_isVibrate = value;
})
}
void _incrementCounter() {
setState(() {
_counter++;
if (_isVibrate) {
Vibration.vibrate(duration: 70);
}
if (_isVibrate) {
Vibration.cancel();
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("My Homepage"),
titleSpacing: 0,
leading: IconButton(
icon: Icon(Icons.settings),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => MySettingPage(
onChange: _onVibrateChange
),
));
},
),
),
body: GestureDetector(
onTap: () {
_incrementCounter();
},
child: Container(
height: double.infinity,
width: double.infinity,
child: Padding(
padding: const EdgeInsets.only(bottom: 120),
child: Column(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: FittedBox(
child: Text(
'$_counter',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 200,
fontFamily: 'DS-Digital',
color: Color(0xFF24F3E2),
),
),
),
),
],
),
),
],
),
),
),
),
);
}
}
using the initState() you set a default value of _isVibrate from the passed value from the widget.isVibrate.
The method _onVibrateChange(bool value) (as a callback) will update the local variable within the state. This method needs to be passed to the MySettingsPage also.
MySettingsPage:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class MySettingPage extends StatefulWidget {
const MySettingPage({Key key, this.onChange}) : super(key: key);
final Function(bool value) onChange;
#override
_MySettingPageState createState() => _MySettingPageState();
}
class _MySettingPageState extends State<MySettingPage> {
bool isVibrate = false;
#override
void initState() {
super.initState();
getSwitchValues();
}
getSwitchValues() async {
isVibrate = await getSwitchState();
setState(() {});
}
Future<bool> saveSwitchState(bool value) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool("switchState", value);
widget.onChange(value);
return prefs.setBool("switchState", value);
}
Future<bool> getSwitchState() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool isVibrate = prefs.getBool("switchState");
return isVibrate;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
titleSpacing: 0,
title: Text("Pengaturan"),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.of(context).pop();
}),
),
body: Container(
padding: EdgeInsets.all(10),
child: ListView(
children: [
SwitchListTile(
title: Text("Getar"),
value: isVibrate,
onChanged: (bool value) async {
setState(() {
isVibrate = value;
saveSwitchState(value);
});
},
),
//
],
),
),
);
}
}
I have included a new variable passed to the Statefulwidget (Function(bool value) onChange), this will be the callback for when the switch changes it's value.
In the method Future saveSwitchState(bool value) async there is a call to the passed callback with the updated value from the SwitchListTiles onChange method.
Hope this clarifies what he meant in his answer.
One way to do this is to use a callback function.
So in MySettingPage(), add a constructor such as below:
MySettingPage({this.callback})
final void Function(bool) callback;
In MySettingPage, if you want to update the value of isVibrate in MyHomePage(), you can call widget.callback(true);
In MyHomePage, you can create a method to update the isVibrate variable.
void _updateIsVibrate(bool isVibrate){//...}
When you call MySettingsPage, you can pass in the method you created.
All my problems related to booleans above have been resolved by implementing MultiProvider. Thanks to the above masters who have helped me. have a nice day

How change the Icon of an IconButton when it is pressed

I want to know how I can change the Icon of an IconButton when it is pressed. (Favorite_border to Favorite). I tried somethings but it doesn't works.
Maybe it is easy but I am a beginner and I don't understand very well how it is works.
Update
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../recyclerview/data.dart';
import 'package:watch/constants.dart';
int itemCount = item.length;
List<bool> selected = new List<bool>();
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
initState() {
for (var i = 0; i < itemCount; i++) {
selected.add(false);
}
super.initState();
}
Icon notFavorite = Icon(Icons.favorite_border, size: 25,);
Icon inFavorite = Icon(Icons.favorite, size: 25,);
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text('Accueil', style: kAppBarStyle,),
//backgroundColor: Colors.white,
elevation: 0,
),
body: ListView.builder(
itemCount: itemCount,
itemBuilder: (BuildContext context, int index) {
return Container(
child: new Row(
children: <Widget>[
//Image
new Container(
margin: new EdgeInsets.all(5.0),
child: new CachedNetworkImage(
imageUrl: item[index].imageURL,
height: MediaQuery.of(context).size.width / 4,
width: MediaQuery.of(context).size.width / 2,
fit: BoxFit.cover,
),
),
//Text
Expanded(
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Spacer(),
//Titre
Container(
padding: const EdgeInsets.only(bottom: 75.0, top: 10.0 ),
child: Text(
item[index].title,
style: kItemTitle,
),
),
//Decription
Container(
padding: const EdgeInsets.only(left: 10.0, top: 10.0),
child:Text(
item[index].description,
style: kItemDescription,
),
),
//Favoris
Spacer(),
GestureDetector(
child: Container(
padding: const EdgeInsets.only(right: 10.0, top: 3.0),
child: selected.elementAt(index) ? inFavorite : notFavorite,
),
onTap: () {
setState(() {
selected[index] = !selected.elementAt(index);
});
},
),
],
),
),
],
),
);
}
)
);
}
}
It is a ListView with Images, Texts and the Favorite Button and it works fine.
First you need a boolean variable.
bool toggle = false;
After that you can use IconButton like this:
IconButton(
icon: toggle
? Icon(Icons.favorite_border)
: Icon(
Icons.favorite,
),
onPressed: () {
setState(() {
// Here we changing the icon.
toggle = !toggle;
});
}),
custom radio button (some IconButton in ListView that change their icons):
main.dart file :
import 'package:flutter/material.dart';
import 'my_home_page.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
my_home_page.dart file:
import 'package:flutter/material.dart';
int itemCount = 5;
List<bool> selected = new List<bool>();
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
initState() {
for (var i = 0; i < itemCount; i++) {
selected.add(false);
}
super.initState();
}
Icon firstIcon = Icon(
Icons.radio_button_on, // Icons.favorite
color: Colors.blueAccent, // Colors.red
size: 35,
);
Icon secondIcon = Icon(
Icons.radio_button_unchecked, // Icons.favorite_border
color: Colors.grey,
size: 35,
);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ListView.builder(
itemCount: itemCount,
itemBuilder: (BuildContext context, int index) {
return IconButton(
icon: selected.elementAt(index) ? firstIcon : secondIcon,
onPressed: () {
try {
// your code that you want this IconButton do
setState(() {
selected[index] = !selected.elementAt(index);
});
print('tap on ${index + 1}th IconButton ( change to : ');
print(selected[index] ? 'active' : 'deactive' + ' )');
} catch (e) {
print(e);
}
},
);
}),
),
);
}
}
Copy paste the code and it will work :)
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: HomeApp(),
);
}
}
class HomeApp extends StatefulWidget {
#override
_HomeAppState createState() => _HomeAppState();
}
class _HomeAppState extends State<HomeApp> {
// Using a Bool
bool addFavorite = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter App :)"),
),
body: Center(
child: IconButton(
icon: Icon(addFavorite ? Icons.favorite : Icons.favorite_border),
onPressed: () {
// Setting the state
setState(() {
addFavorite = !addFavorite;
});
}),
),
);
}
}
Updating the Code for ListView
class _HomeAppState extends State<HomeApp> {
// Using a Bool List for list view builder
List<bool> addFavorite = List<bool>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter App :)"),
),
body: ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
// Setting a bool initially
addFavorite.add(false);
return IconButton(
icon: Icon(addFavorite.elementAt(index)
? Icons.favorite
: Icons.favorite_border),
onPressed: () {
// Setting the state
setState(() {
// Changing icon of specific index
addFavorite[index] =
addFavorite[index] == false ? true : false;
});
});
}),
);
}
}
the IconButton must be in StatefulWidget and use a flag for unselected icon and selected icon:
.
.
.
bool selected = false;
Icon first_icon = Icon(...);
Icon second_icon = Icon(...);
.
.
.
IconButton(
icon: selected
? first_icon
: second_icon,
onPressed: () {
try {
// your code that you want this IconButton do
setState(() {
selected = !selected;
});
} catch(e) {
print(e);
}
}),
for use in ListView:
.
.
.
List<bool> selected = new List<bool>();
Icon first_icon = Icon(...);
Icon second_icon = Icon(...);
.
.
.
ListView.builder(
controller: scrollController,
primary: true,
...
itemCount: _yourListViewLength,
itemBuilder: (BuildContext context, int i) {
selected.add(false);
IconButton(
icon: selected.elementAt(i)
? first_icon
: second_icon,
onPressed: () {
try {
// your code that you want this IconButton do
setState(() {
selected.elementAt(i) = !selected.elementAt(i);
});
} catch(e) {
print(e);
}
}),
},
)
i hope this help you
My code if you want : home_screen.dart
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../recyclerview/data.dart';
import 'package:watch/constants.dart';
class ListViewExample extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new ListViewExampleState();
}
}
class ListViewExampleState extends State<ListViewExample>{
bool addFavorite = false;
Icon notFavorite = Icon(Icons.favorite_border, size: 25,);
Icon inFavorite = Icon(Icons.favorite, size: 25,);
List<Container> _buildListItemsFromItems(){
return item.map((item){
var container = Container(
child: new Row(
children: <Widget>[
//Image
new Container(
margin: new EdgeInsets.all(5.0),
child: new CachedNetworkImage(
imageUrl: item.imageURL,
height: MediaQuery.of(context).size.width / 4,
width: MediaQuery.of(context).size.width / 2,
fit: BoxFit.cover,
),
),
//Text
Expanded(
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Spacer(),
//Titre
Container(
padding: const EdgeInsets.only(bottom: 75.0, top: 5.0 ),
child: Text(
item.title,
style: kItemTitle,
),
),
//Decription
Container(
padding: const EdgeInsets.only(left: 10.0, top: 5.0),
child:Text(
item.description,
style: kItemDescription,
),
),
//Favoris
Spacer(),
GestureDetector(
child: Container(
padding: const EdgeInsets.only(right: 10.0, top: 1.0),
child: addFavorite ? inFavorite : notFavorite,
),
onTap: () {
setState(() {
addFavorite = !addFavorite;
});
},
),
],
),
),
],
),
);
return container;
}).toList();
}
//Scaffold Global
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text('Accueil', style: kAppBarStyle,),
//backgroundColor: Colors.white,
elevation: 0,
),
body: ListView(
children: _buildListItemsFromItems(),
),
);
}
}
It is not an IconButton but just an Icon but it is working.

Bottom TextField with SnackBar

I want to Create an TextField at the bottom of the page like message app.
There is also a IconButton, which adds the entered text into ListView if TextField is not empty. If it is empty then it will show error in SnackBar.
The Problem is the SnackBar stacks on top of TextField. But I want it to be either top or bottom of TextField.
import 'package:flutter/material.dart';
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainPage(),
);
}
}
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
final _textList = <String>[];
TextEditingController _textController;
bool _addText(context, String text) {
print(text);
if (text?.isNotEmpty == true) {
setState(() {
_textList.add(text);
});
return true;
} else {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text("Invalid Text Entered"),
behavior: SnackBarBehavior.fixed,
),
);
return false;
}
}
#override
void initState() {
_textController = TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: ListView.separated(
itemCount: _textList.length,
separatorBuilder: (_, __) => Divider(height: 1.0),
itemBuilder: (context, index) => ListTile(
title: Text("${_textList[index]}"),
),
),
),
_buildBottom(),
],
),
),
);
}
Widget _buildBottom() {
return Material(
elevation: 5.0,
color: Colors.blue[100],
child: Row(
children: <Widget>[
Expanded(
child: TextField(
controller: _textController,
decoration: InputDecoration(
hintText: "Enter Text",
contentPadding: EdgeInsets.symmetric(horizontal: 10.0),
border: InputBorder.none,
),
),
),
Builder(
builder: (context) => IconButton(
icon: Icon(Icons.add),
onPressed: () {
final success = _addText(context, _textController.text);
if (success) _textController.clear();
},
),
),
],
),
);
}
}
This is my code in DartPad
try this,
import 'package:flutter/material.dart';
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainPage(),
);
}
}
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
final _textList = <String>[];
TextEditingController _textController;
bool isVisible = false;
bool _addText(context, String text) {
print(text);
if (text?.isNotEmpty == true) {
setState(() {
_textList.add(text);
});
return true;
} else {
Scaffold.of(context).showSnackBar(
new SnackBar(
content: Text("Invalid Text Entered"),
behavior: SnackBarBehavior.fixed,
duration: Duration(seconds: 3),
onVisible: (() {
setState(() {
isVisible = true;
});
Future.delayed(Duration(seconds: 3)).then((_) => setState(() {
isVisible = false;
}));
}),
),
);
return false;
}
}
#override
void initState() {
_textController = TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: ListView.separated(
itemCount: _textList.length,
separatorBuilder: (_, __) => Divider(height: 1.0),
itemBuilder: (context, index) => ListTile(
title: Text("${_textList[index]}"),
),
),
),
AnimatedContainer(
margin: EdgeInsets.only(bottom: isVisible ? 50 : 0),
child: _buildBottom(),
duration: Duration(milliseconds: 100),
),
],
),
),
);
}
Widget _buildBottom() {
return Material(
elevation: 5.0,
color: Colors.blue[100],
child: Row(
children: <Widget>[
Expanded(
child: TextField(
controller: _textController,
decoration: InputDecoration(
hintText: "Enter Text",
contentPadding: EdgeInsets.symmetric(horizontal: 10.0),
border: InputBorder.none,
),
),
),
Builder(
builder: (context) => IconButton(
icon: Icon(Icons.add),
onPressed: () {
final success = _addText(context, _textController.text);
if (success) _textController.clear();
},
),
),
],
),
);
}
}
Perhaps using Flushbar might help with your problem. There are many properties that you can change, such as flushbarPosition.
It might not solve your problem exactly how you would expect it to but it can make the Flushbar appear from the top instead of the bottom and that's one way around your problem
Flushbar: https://pub.dev/packages/flushbar
I think the best thing you can do here is to change the behaviour to floating
SnackBar(
behavior: SnackBarBehavior.floating,
...