I working creating a favorite page. For each Product Page, I place on app bar a favorite icon that suppose to change and add the specific product to the favorites of the user.
I am failing to change the state of the icon after pressing it.
class FoodDetail extends StatefulWidget {
#override
_FoodDetail createState()=> _FoodDetail();
const FoodDetail({Key key}) : super(key: key);
}
class _FoodDetail extends State<FoodDetail>{
#override
Widget build(BuildContext context) {
FoodNotifier foodNotifier = Provider.of<FoodNotifier>(context);
_onFoodDeleted(Food food) {
Navigator.pop(context);
foodNotifier.deleteFood(food);
}
final _saved = Set<BuildContext>();
final alreadySaved = _saved.contains(context);
return Scaffold(
appBar: AppBar(
title: Text(foodNotifier.currentFood.name),
actions: <Widget>[
// action button
new IconButton(
icon: alreadySaved ? Icon(Icons.star) : Icon(Icons.star_border),
color: alreadySaved ? Colors.yellow[500] : null,
onPressed: (){
setState(() {
if (alreadySaved) {
_saved.remove(context);
} else {
_saved.add(context);
}
});}
Your states doesn't change because you're putting them inside the build method which always reinitialize them.
Put your states _saved, alreadySaved outside the build method.
final _saved = Set<BuildContext>();
final alreadySaved = _saved.contains(context);
#override
Widget build(BuildContext context) {
}
Related
I am trying to change the colour of an icon so black so that it matches the rest of the text.
I use the following statement to determine which icon to use based on the theme:
class ChangeThemeButtonWidget extends StatelessWidget {
const ChangeThemeButtonWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Provider.of<ThemeProvider>(context).isDarkMode
? dark_mode_icon()
: Icons.light_mode),
onPressed: () {
final provider = Provider.of<ThemeProvider>(context, listen: false);
provider.toggleTheme(!provider.isDarkMode);
},
);
}
And
IconData dark_mode_icon() {
//return IconData(0xe37a, fontFamily: 'MaterialIcons');
IconData dark_mode_i = IconData(Icons.dark_mode, color: Colors.black);
return dark_mode_i;
}
My issue is that this returns the error "The argument type 'IconData' can't be assigned to the parameter type 'int'."
How can I edit the style of this icon so that it will change the color correctly?
Many thanks for the help
You are applying color on wrong place. IconData is just widget that describes Icon based on font data. It has nothing to do with color. You have to put those IconData into Icon widget and change color there.
class ChangeThemeButtonWidget extends StatelessWidget {
const ChangeThemeButtonWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Provider.of<ThemeProvider>(context).isDarkMode
? Icons.dark_mode
: Icons.light_mode,
color: #HERE
),
onPressed: () {
final provider = Provider.of<ThemeProvider>(context, listen: false);
provider.toggleTheme(!provider.isDarkMode);
},
);
}
Simply use this code:
IconButton(
onPressed: () {},
color: Colors.blue,
iconSize: 100,
icon: Icon(
Icons.train,
),
)
That is how you can apply color to the iconbutton.
Icons.darkmode is the icon data. IconData() will take an integer so yiu have to write IconData(Icons.darkmode.codePoint)
I need to change implement custom Icons for default button and drawer button for all pages in my project.
I know we have the option of using leading property, however, this only affects that certain page.
How can we change the default back button and open drawer button of AppBar in Flutter for the whole app?
Unfortunately, there is not a property called defaultBackButton or defaultDrawerButton.
So, in order to change these defaults in the whole app, we can create a CustomAppBar which and set Icons as we wish.
Please click here to see Demo on DartPad and test it yourself.
For a bit longer description, checkout my Medium story.
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
final Widget? leading;
final Widget? title;
final bool? centerTitle;
final bool automaticallyImplyLeading;
const CustomAppBar({
Key? key,
this.leading,
this.title,
this.centerTitle,
this.automaticallyImplyLeading = true,
}) : super(key: key);
#override
Widget build(BuildContext context) {
/// This part is copied from AppBar class
final ScaffoldState? scaffold = Scaffold.maybeOf(context);
final bool hasDrawer = scaffold?.hasDrawer ?? false;
final ModalRoute<dynamic>? parentRoute = ModalRoute.of(context);
final bool canPop = parentRoute?.canPop ?? false;
Widget? leadingIcon = leading;
if (leadingIcon == null && automaticallyImplyLeading) {
if (hasDrawer) {
leadingIcon = IconButton(
icon: const Icon(Icons.mood_sharp, color: Colors.yellowAccent),
onPressed: () => Scaffold.of(context).openDrawer(),
);
} else {
if (canPop) {
leadingIcon = IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(
Icons.sentiment_dissatisfied_sharp,
color: Colors.red,
),
);
}
}
}
return AppBar(
leading: leadingIcon,
title: title,
centerTitle: centerTitle,
);
}
#override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}
So, I'm new to Flutter and was trying to code a simple notes app to learn my way around it. The layout of the app is a HomePage with a ListView of NoteTiles that when tapped open a corresponding NotePage where you can write down things. The issue I got is that every time I leave the NotePage and then re-open it from the HomePage, the NotePage loses its content.
My first idea was to keep the content in the corresponding NoteTile so that when I leave the NotePage I would pop with the content, and when needed I would push to the NotePage with the previously saved content. The problem is that I didn't find any simple way to push and set the content. I've seen there are Notification and Route methods but they come with quite a lot of boilerplate and they look like it's more for passing data from child to parent, which I can do easily when popping.
So, is there a way to avoid the reset of the content of the NotePage? Or maybe is there a simple way to initState with the content previously saved?
Here is the code I have so far:
class NoteTile extends ListTile {
final NotePage note;
final Text title;
final BuildContext context;
NoteTile(this.title, this.note, this.context) : super(
title: title,
onTap: () => {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => note),
),
},
onLongPress: () => null,
);
void switchToNote() async {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => note),
);
}
}
onLongPress will later be used to delete the note.
class NotePage extends StatefulWidget {
final String title;
NotePage({Key key, this.title}) : super(key: key);
#override
_NotePageState createState() => _NotePageState();
}
class _NotePageState extends State<NotePage> {
TextEditingController _controller;
String _value;
void initState() {
super.initState();
_controller = TextEditingController();
_controller.addListener(_updateValue);
_value = '';
}
void dispose() {
_controller.dispose();
super.dispose();
}
void _updateValue(){
_value = _controller.text;
}
Future<bool> _onWillPop() async {
Navigator.pop(context, _value);
return true;
}
#override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: TextField(
decoration: InputDecoration(
border: InputBorder.none,
hintStyle: TextStyle(fontStyle: FontStyle.italic),
hintText: 'New note',
),
maxLines: null,
controller: _controller,
),
padding: EdgeInsets.all(12.0),
),
),
onWillPop: _onWillPop,
);
}
}
The _onWillPop is to send back the content to the NoteTile, which currently disregards the return data because I failed to find a way to use that data later when pushing the NotePage again.
ListTile is StatelessWidget widget, so you cannot reserve the state, the NoteTile should be StatefulWidget.
Here is a sample of NoteTile:
class NoteTile extends StatefulWidget {
final Text title;
const NoteTile({Key key, this.title}) : super(key: key);
#override
State<StatefulWidget> createState() {
return _NoteTileState();
}
}
class _NoteTileState extends State<NoteTile> {
String _result;
#override
void initState() {
super.initState();
_result = "";
}
void _onOpenNote() async {
String result = await Navigator.of(context).push(
MaterialPageRoute(builder: (context) => NotePage(title: _result)),
);
if (result != null) {
_result = result;
}
}
#override
Widget build(BuildContext context) {
return ListTile(
title: widget.title,
onTap: _onOpenNote,
);
}
}
And NotePage should be edit in some lines:
TextEditingController can have initialize string & have to initialize by title:
_controller = TextEditingController(text: widget.title);
Navigator.pop can post result and you did it correctly, but if you want to get the result, should wait for Navigator.of(context).push, because its run in another thread.
A simple, but very complicated question: What’s the best way to add a tap to focus functionality for the Flutter camera?
I’ve searched the entire World Wide Web about elegant solutions, but I found nothing.
Do you have an idea?
I might be late but you can try adv_camera package.
Here is a simple example:
import 'package:adv_camera/adv_camera.dart';
import 'package:flutter/material.dart';
class CameraApp extends StatefulWidget {
final String id;
const CameraApp({Key? key, required this.id}) : super(key: key);
#override
_CameraAppState createState() => _CameraAppState();
}
class _CameraAppState extends State<CameraApp> {
List<String> pictureSizes = <String>[];
String? imagePath;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('AdvCamera Example'),
),
body: SafeArea(
child: AdvCamera(
initialCameraType: CameraType.rear,
onCameraCreated: _onCameraCreated,
onImageCaptured: (String path) {
if (this.mounted)
setState(() {
imagePath = path;
});
},
cameraPreviewRatio: CameraPreviewRatio.r16_9,
focusRectColor: Colors.purple,
focusRectSize: 200,
),
),
floatingActionButton: FloatingActionButton(
heroTag: "capture",
child: Icon(Icons.camera),
onPressed: () {
cameraController!.captureImage();
},
),
);
}
AdvCameraController? cameraController;
_onCameraCreated(AdvCameraController controller) {
this.cameraController = controller;
this.cameraController!.getPictureSizes().then((pictureSizes) {
setState(() {
this.pictureSizes = pictureSizes ?? <String>[];
});
});
}
}
I am using a ListView with selectable items similar to this example.
Each stateful widget in the ListView has a _selected boolean to determine it's selected status which is flipped when the item is tapped.
When the user is in selection mode, there is a "back" option in the app bar. Determining when the back button is pressed and handling underlying core logic is working fine. I just want to reset the _selected flag on each individual list item so that they no long display as selected. You can see in the included gif that once back is pressed, the ListView items remain selected.
I am obviously missing something extremely basic.
The underlying question is, how do I trigger a reset of a ListView children items programatically.
Edit: Sample code added
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'List selection demo',
home: new MyHomePage(title: 'List selection demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List<String> playerList = [
"Player 1",
"Player 2",
"Player 3",
"Player 4"
];
List<String> selectedPlayers = [];
bool longPressFlag = false;
void longPress() {
setState(() {
if (selectedPlayers.isEmpty) {
longPressFlag = false;
} else {
longPressFlag = true;
}
});
}
void clearSelections(){
setState(() {
selectedPlayers.clear();
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(selectedPlayers.length == 0?widget.title: selectedPlayers.length.toString() + " selected"),
leading: selectedPlayers.length == 0? null: new IconButton(
icon: new Icon(Icons.arrow_back),
onPressed: () {clearSelections();
})),
body: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new PlayerItem(
playerName: playerList[index],
longPressEnabled: longPressFlag,
callback: () {
if (selectedPlayers.contains(playerList[index])) {
selectedPlayers.remove(playerList[index]);
} else {
selectedPlayers.add(playerList[index]);
}
longPress();
});
},
itemCount: playerList.length,
));
}
}
class PlayerItem extends StatefulWidget {
final String playerName;
final bool longPressEnabled;
final VoidCallback callback;
const PlayerItem(
{Key key, this.playerName, this.longPressEnabled, this.callback})
: super(key: key);
#override
_PlayerItemState createState() => new _PlayerItemState();
}
class _PlayerItemState extends State<PlayerItem> {
bool selected = false;
#override
Widget build(BuildContext context) {
return new GestureDetector(
onLongPress: () {
setState(() {
selected = !selected;
});
widget.callback();
},
onTap: () {
if (widget.longPressEnabled) {
setState(() {
selected = !selected;
});
widget.callback();
} else {
final snackBar = SnackBar(content: Text(widget.playerName + " tapped"));
Scaffold.of(context).showSnackBar(snackBar);
}
},
child: new Card(
color: selected ? Colors.grey[300] : Colors.white,
elevation: selected ? 4.0 : 1.0,
margin: const EdgeInsets.all(4.0),
child: new ListTile(
leading: new CircleAvatar(
child: new Text(widget.playerName.substring(0, 1)),
),
title: new Text(widget.playerName),
)));
}
}
You have to call following method for reset widgets.
setState(() {
});