Flutter Provider/Consumer UI not Updating with Data Change - flutter

I am new to Flutter and working on developing my first application. I an having trouble updating my UI based on data changes. I followed this tutorial: https://www.filledstacks.com/post/flutter-architecture-my-provider-implementation-guide/ (and added extra features with https://www.filledstacks.com/post/flutter-provider-architecture-sharing-data-across-your-models/), which walks through a MVVM setup using the GetIt library.
I have successfully passed the data through the viewmodels and services, but I can't get my UI to update. Essentially, I want a user to be able to tap the movie on their watchlist and remove it. This works in a data sense, but my Watchlist UI isn't updating when I pop back to it. My entire project is available at https://github.com/n0ahth0mas/movie_night, but the core files to this problem are listed here:
locator.dart
...
...
GetIt locator = GetIt.instance;
void setupLocator() {
locator.registerLazySingleton(() => AuthenticationService());
locator.registerLazySingleton(() => WatchlistService());
locator.registerLazySingleton(() => Api());
locator.registerLazySingleton(() => LoginModel());
locator.registerFactory(() => HomeModel());
locator.registerFactory(() => RemoveButtonModel());
locator.registerFactory(() => WatchlistModel());
}
watchlist_service.dart
...
...
class WatchlistService {
Api _api = locator<Api>();
List<Movie> _watchlist = [];
List<Movie> get watchlist => _watchlist;
Future getUserWatchlist(User user) async {
for (String id in user.watchlistId) {
_watchlist.add(await _api.getMovie(id));
}
}
bool inList(Movie movie) {
return _watchlist.contains(movie);
}
void editList(Movie movie, bool remove) {
if (remove) {
_watchlist.remove(movie);
print("removed " + movie.title + " from list in watchlist service");
} else {
_watchlist.add(movie);
print("added " + movie.title + " to list in watchlist service");
}
}
}
watchlist_model.dart
...
...
class WatchlistModel extends BaseModel {
WatchlistService _watchlistService = locator<WatchlistService>();
List<Movie> get watchlist => _watchlistService.watchlist;
Future getWatchlist(User user) async {
setState(ViewState.Busy);
await _watchlistService.getUserWatchlist(user);
setState(ViewState.Idle);
}
}
watchlist_view.dart
...
...
class WatchlistView extends StatelessWidget {
Widget _createMovieCard(Movie movie, BuildContext context) {
return CupertinoButton(
onPressed: () => _getInfo(movie, context),
child: Image.network(movie.getPoster(185)),
);
}
_getInfo(Movie movie, BuildContext context) {
Navigator.pushNamed(
context,
"details",
arguments: movie,
);
}
#override
Widget build(BuildContext context) {
return BaseView<WatchlistModel>(
onModelReady: (model) => model.getWatchlist(Provider.of<User>(context)),
builder: (context, model, child) => Scaffold(
body: model.state == ViewState.Idle
? CustomScrollView(
primary: false,
slivers: <Widget>[
CupertinoSliverNavigationBar(
largeTitle: Text("My Watchlist"),
trailing: Material(
color: Colors.transparent,
child: IconButton(
icon: Icon(CupertinoIcons.add_circled_solid),
onPressed: () =>
Navigator.pushNamed(context, 'search'))),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio:
MediaQuery.of(context).size.width /
(MediaQuery.of(context).size.height / 1.7)),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return _createMovieCard(
model.watchlist[index], context);
}, childCount: model.watchlist.length),
),
],
)
: Center(child: CircularProgressIndicator())));
}
}
removebtn_model.dart
...
...
class RemoveButtonModel extends BaseModel {
WatchlistService _watchlistService = locator<WatchlistService>();
//bool watchlistStatus()
void removeMovie(Movie movie) {
print("removed " + movie.title + " from list in remove button model");
_watchlistService.editList(movie, true);
notifyListeners();
}
void addMovie(Movie movie) {
print("added " + movie.title + " to list in remove button model");
_watchlistService.editList(movie, false);
notifyListeners();
}
}
details_view.dart
...
...
class DetailsView extends StatelessWidget {
_makeStars(double voteAverage) {
double trueRating = voteAverage / 2;
print(trueRating);
return GFRating(
color: Colors.yellowAccent[700],
value: trueRating,
size: GFSize.SMALL,
allowHalfRating: true,
);
}
_backdrop(String link) {
if (link != null) {
return Image.network("http://image.tmdb.org/t/p/w780/$link");
} else
return Image.asset("assets/images/transparent.png");
}
#override
Widget build(BuildContext context) {
Movie movie = ModalRoute.of(context).settings.arguments;
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
CupertinoSliverNavigationBar(
largeTitle: Text(movie.title),
trailing:
Material(color: Colors.transparent, child: RemoveButton(movie)),
),
SliverToBoxAdapter(
child: Column(
children: <Widget>[
Padding(padding: EdgeInsets.only(top: 20)),
Center(child: Image.network(movie.getPoster(342))),
Center(
child: Container(
padding: EdgeInsets.all(10),
child: Center(
child: Text(movie.title,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 40, fontWeight: FontWeight.w800))),
)),
Container(
padding: EdgeInsets.only(bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
_makeStars(movie.voteAverage),
Text("platform",
style: Theme.of(context).textTheme.headline5),
],
),
),
Container(
padding: EdgeInsets.fromLTRB(20, 0, 10, 50),
child: Text(movie.overview),
),
_backdrop(movie.backdropPath)
],
))
],
),
);
}
}
remove_btn.dart
...
...
class RemoveButton extends StatelessWidget {
final Movie movie;
RemoveButton(this.movie);
_showDialog(String id, bool inWatchlist, BuildContext context,
RemoveButtonModel model) {
if (inWatchlist) {
showDialog(
context: context,
child: CupertinoAlertDialog(
title: Text("Remove from your watchlist?"),
actions: <Widget>[
CupertinoDialogAction(
isDefaultAction: true,
onPressed: () =>
Navigator.of(context, rootNavigator: true).pop(),
child: Text("Cancel"),
),
CupertinoDialogAction(
textStyle: TextStyle(color: Colors.red),
onPressed: () => _deletelist(id, context, model),
child: Text("Remove"),
)
]));
} else {
showDialog(
context: context,
child: CupertinoAlertDialog(
title: Text("Add to your watchlist?"),
actions: <Widget>[
CupertinoDialogAction(
onPressed: () =>
Navigator.of(context, rootNavigator: true).pop(),
child: Text("Cancel"),
),
CupertinoDialogAction(
isDefaultAction: true,
onPressed: () => _addList(id, context, model),
child: Text("Add"),
)
]));
}
}
_deletelist(String id, BuildContext context, RemoveButtonModel model) {
model.removeMovie(movie);
Provider.of<User>(context).deleteFromWatchlist(id);
//Navigator.pushNamed(context, 'watchlist');
Navigator.popUntil(context, ModalRoute.withName('watchlist'));
}
_addList(String id, BuildContext context, RemoveButtonModel model) {
model.addMovie(movie);
Provider.of<User>(context).addToWatchList(id);
Navigator.popUntil(context, ModalRoute.withName('watchlist'));
}
Widget _rightBtn(Movie movie, BuildContext context, RemoveButtonModel model) {
if (Provider.of<User>(context).inWatchlist(movie)) {
return IconButton(
icon: Icon(Icons.remove_circle),
color: Colors.blue,
onPressed: () =>
_showDialog(movie.id.toString(), true, context, model));
} else {
return IconButton(
icon: Icon(Icons.add_circle),
color: Colors.blue,
onPressed: () =>
_showDialog(movie.id.toString(), false, context, model));
}
}
#override
Widget build(BuildContext context) {
return BaseView<RemoveButtonModel>(
builder: (context, model, child) => _rightBtn(movie, context, model),
);
}
}
Thanks for your help! I've been stuck on this for the past 3 days and cannot figure out what I'm doing wrong.

Related

Flutter Sqflite Toggling between Screens based on Login Status creates null operator used on null value error

I am trying to toggle between Login Screen and HomeScreen based on the user status. The logic seems to be working as long as I don't put HomeScreen.
I replaced HomeScreen with a different screen to check and the app works as it should. It displays different screens on hot restart based on the user's login status. But as soon as I try to put HomeScreen I get null operator used on null value error.
Here is the toggle logic.
class Testing extends StatefulWidget {
const Testing({super.key});
#override
State<Testing> createState() => _TestingState();
}
class _TestingState extends State<Testing> {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: TodoServiceHelper().checkifLoggedIn(),
builder: ((context, snapshot) {
if (!snapshot.hasData) {
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
}
if (snapshot.hasError) {
print(snapshot.hasError);
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
}
if (snapshot.data!.isNotEmpty) {
print(snapshot.data);
return RegisterPage();
// returning HomePage gives null check operator used on null value error
} else
return Login();
}),
);
}
}
Here is the HomeScreen
class HomePage extends StatefulWidget {
String? username;
HomePage({this.username});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final GlobalKey<FormState> formKey = GlobalKey();
TextEditingController termController = TextEditingController();
void clearText() {
termController.clear();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
onPressed: () {
User loginUser =
User(username: widget.username.toString(), isLoggedIn: false);
TodoServiceHelper().updateUserName(loginUser);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (BuildContext context) => Login()));
},
icon: Icon(Icons.logout),
color: Colors.white,
)
],
title: FutureBuilder(
future: TodoServiceHelper().getTheUser(widget.username!),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
}
return Text(
'Welcome ${snapshot.data!.username}',
style: TextStyle(color: Colors.white),
);
}),
),
body: SingleChildScrollView(
child: Column(children: [
Column(
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: Form(
key: formKey,
child: Column(
children: <Widget>[
TextFormField(
controller: termController,
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
enabledBorder: OutlineInputBorder(),
labelText: 'search todos',
),
),
TextButton(
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ShowingSerachedTitle(
userNamee: widget.username!,
searchTerm: termController.text,
)),
);
print(termController.text);
clearText();
setState(() {});
},
child: Text(
'Search',
)),
Divider(
thickness: 3,
),
],
),
),
),
],
),
Container(
child: Stack(children: [
Positioned(
bottom: 0,
child: Text(
' done Todos',
style: TextStyle(fontSize: 12),
),
),
IconButton(
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
CheckingStuff(userNamee: widget.username!)),
);
setState(() {});
},
icon: Icon(Icons.filter),
),
]),
),
Divider(
thickness: 3,
),
Container(
child: TodoListWidget(name: widget.username!),
height: 1000,
width: 380,
)
]),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Color.fromARGB(255, 255, 132, 0),
onPressed: () async {
await showDialog(
barrierDismissible: false,
context: context,
builder: ((context) {
return AddNewTodoDialogue(name: widget.username!);
}),
);
setState(() {});
},
child: Icon(Icons.add),
),
);
}
}
The function used to return user with loginStatus true
Future<List<User>> checkifLoggedIn() async {
final Database db = await initializeDB();
final List<Map<String, Object?>> result = await db.query(
'users',
where: 'isLoggedIn = ?',
whereArgs: ['1'],
);
List<User> filtered = [];
for (var item in result) {
filtered.add(User.fromMap(item));
}
return filtered;
}
the problem is here
you used ! sign on a nullable String , and this string is nullable,
try to use this operation (??) so make it
widget.username??"" by this line you will check if the user name is null it will be replaced by an empty string.

RangeError (index) Flutter

I am getting an error and red screen while trying to click a button. RangeError (index): Invalid value: Valid value range is empty: 0. I do not know how to fix this error because nothing is flagged until I run my emulator. I have attached my
If I need to add some code please let me know I am more than willing to. Thank you!
home_page.dart
import 'package:flutter/material.dart';
import 'package:timestudy_test/pages/study_page.dart';
import 'package:timestudy_test/pages/timer_page.dart';
import 'package:timestudy_test/viewmodels/study_viewmodel.dart';
class HomePage extends StatefulWidget {
#override
State createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
TextEditingController textController = new TextEditingController();
late String filter;
#override
void initState() {
textController.addListener(() {
setState(() {
filter = textController.text;
});
});
super.initState();
}
#override
void dispose() {
textController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text('TimeStudyApp'),
),
body: Material(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0),
child: TextField(
style: TextStyle(fontSize: 18.0),
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
suffixIcon: IconButton(
icon: Icon(Icons.close),
onPressed: () {
textController.clear();
FocusScope.of(context).requestFocus(FocusNode());
},
),
hintText: "Search...",
),
controller: textController,
)),
Expanded(
child: StudyViewModel.studies.length > 0
? ListView.builder(
itemCount: StudyViewModel.studies.length,
itemBuilder: (BuildContext context, int index) {
if (filter == null || filter == "") {
return buildRow(context, index);
} else {
if (StudyViewModel.studies[index].name
.toLowerCase()
.contains(filter.toLowerCase())) {
return buildRow(context, index);
} else {
return Container();
}
}
},
)
: Center(
child: Text('No studies found!'),
),
)
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () async {
int? nullableInterger;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StudyPage(
title: 'Add a study',
selected: nullableInterger ?? 0,
)));
},
),
);
}
Widget buildRow(BuildContext context, int index) {
return ExpansionTile(
title: Text(StudyViewModel.studies[index].name, style: TextStyle(fontSize: 18.0)),
children: <Widget>[
ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: StudyViewModel.studies[index].tasks.length,
itemBuilder: (context, int taskIndex) {
return ListTile(
title: Text(StudyViewModel.studies[index].tasks[taskIndex].name),
contentPadding: EdgeInsets.symmetric(horizontal: 32.0),
subtitle: Text(
StudyViewModel.studies[index].tasks[taskIndex].elapsedTime),
trailing: IconButton(
icon: Icon(
Icons.timer
),
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TimerPage(
task: StudyViewModel
.studies[index].tasks[taskIndex])));
},
),
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
icon: Icon(
Icons.edit
),
onPressed: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StudyPage(
title: StudyViewModel.studies[index].name,
selected: index)));
},
),
IconButton(
icon: Icon(
Icons.delete
),
onPressed: () async {
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Do you wish to delete this study?'),
actions: <Widget>[
FlatButton(
child: Text('Accept'),
onPressed: () async {
StudyViewModel.studies.removeAt(index);
await StudyViewModel.saveFile();
Navigator.of(context).pop();
},
),
FlatButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
});
},
),
],
),
],
);
}
}
study_page.dart
import 'package:flutter/material.dart';
import 'package:timestudy_test/models/study.dart';
import 'package:timestudy_test/models/task.dart';
import 'package:timestudy_test/viewmodels/study_viewmodel.dart';
class StudyPage extends StatefulWidget {
final String title;
final int selected;
StudyPage({required this.title, required this.selected});
#override
State createState() => StudyPageState();
}
class StudyPageState extends State<StudyPage> {
late Study study;
late TextField nameField;
TextEditingController nameController = new TextEditingController();
late TextField taskNameField;
TextEditingController taskNameController = new TextEditingController();
#override
void initState() {
nameField = new TextField(
controller: nameController,
decoration: InputDecoration(
labelText: 'Study name'),
);
taskNameField = new TextField(
controller: taskNameController,
decoration:
InputDecoration(labelText: 'Task name'),
);
if(widget.selected != null) {
study = StudyViewModel.studies[widget.selected];
nameController.text = study.name;
} else {
study = new Study(
name: "",
tasks: <Task>[]
);
}
super.initState();
}
#override
void dispose() {
nameController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text(widget.title),
),
body: Material(
child: Padding(padding: EdgeInsets.all(16.0), child: Column(
children: <Widget>[
Padding(padding: EdgeInsets.only(bottom: 8.0), child: nameField),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('Tasks:', style: TextStyle(fontSize: 18.0),),
IconButton(
icon: Icon(Icons.add),
onPressed: () async {
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Add a task'),
content: taskNameField,
actions: <Widget>[
FlatButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
FlatButton(
child: Text('Accept'),
onPressed: () {
if(taskNameController.text == ""){
errorDialog(context, 'Please enter a task name!');
} else {
setState(() {
study.tasks.add(new Task(
name: taskNameController.text,
elapsedTime:
StudyViewModel.milliToElapsedString(
0)));
taskNameController.clear();
});
Navigator.of(context).pop();
}
},
),
],
);
});
},
)
],
),
Expanded(
child: ListView.builder(
itemCount: study.tasks.length,
itemBuilder: (context, int index) {
return ListTile(
title: Text(study.tasks[index].name),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
study.tasks.removeAt(index);
});
},
),
);
},
),
), Spacer(),
Center(
child: RaisedButton(
color: Theme.of(context).accentColor,
child: Text('Save'),
onPressed: () async {
if (nameController.text == "") {
errorDialog(context, 'Please enter a study name!');
} else {
if (study.tasks.length < 1) {
errorDialog(context, 'Please add at least one task!');
} else {
study.name = nameController.text;
if (widget.selected != null) {
StudyViewModel.studies[widget.selected] = study;
await StudyViewModel.saveFile();
Navigator.of(context).pop();
} else {
if (StudyViewModel.checkName(nameController.text)) {
errorDialog(context, 'Study name already taken!');
} else {
StudyViewModel.studies.add(study);
await StudyViewModel.saveFile();
Navigator.of(context).pop();
}
}
}
}
},
))
],
),
)));
}
void errorDialog(BuildContext context, String message) async {
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text('Close'),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
}
);
}
}
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () async {
int? nullableInterger;
// the issue is here you need to assign value
// the nullableInterger is use here as nothing.. declare it on state level and
// assign it when your listview builder done so the value of nullable integer is
// changed and passing value as argument will not be 0 and this error will not appear again
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StudyPage(
title: 'Add a study',
selected: nullableInterger ?? 0,
)));
},
),

ValueListenableBuilder<String> widget cannot be marked as needing to build. setState() or markNeedsBuild() called during build. File_manager flutter

I am using the file_manager package. When I try to run the example code given, I get the below error. I'm not familiar with ValueNotifier and ValueListenableBuilder so I can't figure out, what seems to be the issue here.
The following assertion was thrown while dispatching notifications for ValueNotifier<String>:
setState() or markNeedsBuild() called during build.
This ValueListenableBuilder<String> widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
Below is the code. I have not added a single line of my own code. It is package example code
import 'dart:io';
import 'package:file_manager/file_manager.dart';
import 'package:flutter/material.dart';
class HomeView extends StatelessWidget {
final FileManagerController controller = FileManagerController();
HomeView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
// Creates a widget that registers a callback to veto attempts by the user to dismiss the enclosing
// or controllers the system's back button
return WillPopScope(
onWillPop: () async {
if (await controller.isRootDirectory()) {
return true;
} else {
controller.goToParentDirectory();
return false;
}
},
child: Scaffold(
appBar: AppBar(
actions: [
IconButton(
onPressed: () => createFolder(context),
icon: const Icon(Icons.create_new_folder_outlined),
),
IconButton(
onPressed: () => sort(context),
icon: const Icon(Icons.sort_rounded),
),
IconButton(
onPressed: () => selectStorage(context),
icon: const Icon(Icons.sd_storage_rounded),
)
],
title: ValueListenableBuilder<String>(
valueListenable: controller.titleNotifier,
builder: (context, title, _) => Text(title),
),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () async {
await controller.goToParentDirectory();
},
),
),
body: Container(
margin: const EdgeInsets.all(10),
child: FileManager(
controller: controller,
builder: (context, snapshot) {
final List<FileSystemEntity> entities = snapshot;
return ListView.builder(
itemCount: entities.length,
itemBuilder: (context, index) {
FileSystemEntity entity = entities[index];
return Card(
child: ListTile(
leading: FileManager.isFile(entity)
? const Icon(Icons.feed_outlined)
: const Icon(Icons.folder),
title: Text(FileManager.basename(entity)),
subtitle: subtitle(entity),
onTap: () async {
if (FileManager.isDirectory(entity)) {
// open the folder
controller.openDirectory(entity);
// delete a folder
// await entity.delete(recursive: true);
// rename a folder
// await entity.rename("newPath");
// Check weather folder exists
// entity.exists();
// get date of file
// DateTime date = (await entity.stat()).modified;
} else {
// delete a file
// await entity.delete();
// rename a file
// await entity.rename("newPath");
// Check weather file exists
// entity.exists();
// get date of file
// DateTime date = (await entity.stat()).modified;
// get the size of the file
// int size = (await entity.stat()).size;
}
},
),
);
},
);
},
),
)),
);
}
Widget subtitle(FileSystemEntity entity) {
return FutureBuilder<FileStat>(
future: entity.stat(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (entity is File) {
int size = snapshot.data!.size;
return Text(
"${FileManager.formatBytes(size)}",
);
}
return Text(
"${snapshot.data!.modified}",
);
} else {
return const Text("");
}
},
);
}
selectStorage(BuildContext context) {
showDialog(
context: context,
builder: (context) => Dialog(
child: FutureBuilder<List<Directory>>(
future: FileManager.getStorageList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final List<FileSystemEntity> storageList = snapshot.data!;
return Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: storageList
.map((e) => ListTile(
title: Text(
"${FileManager.basename(e)}",
),
onTap: () {
controller.openDirectory(e);
Navigator.pop(context);
},
))
.toList()),
);
}
return const Dialog(
child: CircularProgressIndicator(),
);
},
),
),
);
}
sort(BuildContext context) async {
showDialog(
context: context,
builder: (context) => Dialog(
child: Container(
padding: const EdgeInsets.all(10),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: const Text("Name"),
onTap: () {
controller.sortedBy = SortBy.name;
Navigator.pop(context);
}),
ListTile(
title: const Text("Size"),
onTap: () {
controller.sortedBy = SortBy.size;
Navigator.pop(context);
}),
ListTile(
title: const Text("Date"),
onTap: () {
controller.sortedBy = SortBy.date;
Navigator.pop(context);
}),
ListTile(
title: const Text("type"),
onTap: () {
controller.sortedBy = SortBy.type;
Navigator.pop(context);
}),
],
),
),
),
);
}
createFolder(BuildContext context) async {
showDialog(
context: context,
builder: (context) {
TextEditingController folderName = TextEditingController();
return Dialog(
child: Container(
padding: const EdgeInsets.all(10),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: TextField(
controller: folderName,
),
),
ElevatedButton(
onPressed: () async {
try {
// Create Folder
await FileManager.createFolder(
controller.getCurrentPath, folderName.text);
// Open Created Folder
controller.setCurrentPath =
controller.getCurrentPath + "/" + folderName.text;
} catch (e) {}
Navigator.pop(context);
},
child: const Text('Create Folder'),
)
],
),
),
);
},
);
}
}

Flutter navigate List tile to a list detail page

have a to do app which lists all todos in in a screen
This is my listview
class assignedTask extends StatefulWidget {
static const String id = 'assignedTask';
#override
_assignedTaskState createState() => _assignedTaskState();
}
class _assignedTaskState extends State<assignedTask> {
String Title;
String Summary;
var tasks;
crudMedthods crudObj = new crudMedthods();
var documentID;
var documents;
Future<bool> addDialog(BuildContext context) async {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Add Data', style: TextStyle(fontSize: 15.0)),
content: Container(
height: 125.0,
width: 150.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
decoration: InputDecoration(hintText: 'Enter Title'),
onChanged: (value) {
this.Title = value;
},
),
SizedBox(height: 5.0),
TextField(
decoration: InputDecoration(hintText: 'Enter Summary'),
onChanged: (value) {
this.Summary = value;
},
),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Add'),
textColor: Colors.blue,
onPressed: () {
Navigator.of(context).pop();
crudObj.addData({
'Title': this.Title,
'Summary': this.Summary
}).then((result) {
dialogTrigger(context);
}).catchError((e) {
print(e);
});
},
)
],
);
});
}
Future<bool> updateDialog(BuildContext context, selectedDoc) async {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Update Data', style: TextStyle(fontSize: 15.0)),
content: Container(
height: 125.0,
width: 150.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
decoration: InputDecoration(hintText: 'Title'),
onChanged: (value) {
this.Title = value;
},
),
SizedBox(height: 5.0),
TextField(
decoration: InputDecoration(hintText: 'Enter Summary'),
onChanged: (value) {
this.Summary = value;
},
),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Update'),
textColor: Colors.blue,
onPressed: () {
Navigator.of(context).pop();
crudObj.updateData(selectedDoc, {
'Title': this.Title,
'Summary': this.Summary
}).then((result) {
// dialogTrigger(context);
}).catchError((e) {
print(e);
});
},
)
],
);
});
}
Future<bool> dialogTrigger(BuildContext context) async {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Job Done', style: TextStyle(fontSize: 15.0)),
content: Text('Added'),
actions: <Widget>[
FlatButton(
child: Text('Alright'),
textColor: Colors.blue,
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
});
}
#override
void initState() {
crudObj.getData().then((results) {
setState(() {
tasks = results;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text('Assigned Tasks'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () {
addDialog(context);
},
),
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
crudObj.getData().then((results) {
setState(() {
tasks = results;
});
});
},
)
],
),
body: _TaskList(),
);
}
Widget _TaskList() {
if (tasks != null) {
return StreamBuilder(
stream: tasks,
// ignore: missing_return
builder: (context, snapshot) {
if (snapshot.data != null) {
return ListView.builder(
itemCount: snapshot.data.documents.length,
padding: EdgeInsets.all(7.0),
itemBuilder: (context, i) {
return ListTile(
title: Text(snapshot.data.documents[i].data["Title"]??""),
subtitle: Text(snapshot.data.documents[i].data["Summary"]??""),
/*onTap: () {
*//*
updateDialog(
context, snapshot.data.documents[i].documentID);
},*/
onTap: (){
Navigator.push(context,MaterialPageRoute(builder:(context)=>detailview(context,snapshot.data.douments[i].documentid)));
},
onLongPress: () {
crudObj.deleteData(snapshot.data.documents[i].documentID);
},
);
},
);
}
return CircularProgressIndicator();
},
);
} else {
return Text('Loading, Please wait..');
}
}
}
When i press ontap it should go to the detail page which shows details of only that particular task(note: i now have only the values Title and summary for now, i will add rest later they should show in the detail page)
My detailview is as follows
class detailview extends StatefulWidget {
static const String id = 'detailview';
detailview(BuildContext context, selectedDoc);
#override
_detailviewState createState() => _detailviewState();
}
class _detailviewState extends State<detailview> {
final TaskReference = FirebaseDatabase.instance.reference().child('Task');
crudMedthods crudObj1 = new crudMedthods();
/*TextEditingController _titleController;
TextEditingController _descriptionController;*/
var tasks;
/*#override
void initState() {
super.initState();
//_titleController = new TextEditingController(text: widget.tasks.Title);
//_descriptionController = new TextEditingController(text: widget.tasks.Summary);
}*
*/
#override
void initState() {
crudObj1.getDetail(tasks).then((results) {
setState(() {
tasks = results;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text('Details of Tasks'),
actions: <Widget>[
],
),
body: Column(
children: <Widget>[
TextField(
//controller: _titleController,
),
],
)
);
}
}
How could i access the values of particular tapped value in detail page.
Just pass the DocumentSnapshot to your detail page like this:
onTap: (){
Navigator.push(context,MaterialPageRoute(builder:(context)=>DetailPage(context,snapshot.data.douments[i])));
},
In your detail page receive the DocumentSnapshot and access anything you are getting from it.
class DetailPage extends StatelessWidget {
DocumentSnapshot detailDocument;
DetailPage(this.detailDocument);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(detailDocument.data["Title"]),
),
body: Center(
child: Text(detailDocument.data["Summary"]),
),
);
}
}

Flutter: setState on showDialog()

I need to update the value within the showDialog() [inside confirmBox()] function, when ' + ' or ' - ' is pressed; and render it onto the Container Widget. The setState() doesn't seem to work on that pop up Container.
How do I go about doing this?
(I'm a beginner)
int _n = 0; //counter variable
void add() {
setState(() {
_n++;
});
}
void minus() {
setState(() {
if (_n != 0)
_n--;
});
}
void confirmBox() {
showDialog(
context: context,
builder: (BuildContext context){
return Container(
child: Scaffold(
body: Column(
children: <Widget>[
Center(
child: Column(
children: <Widget>[
FloatingActionButton(
onPressed: add,
child: Icon(Icons.add, color: Colors.black,),
backgroundColor: Colors.white,),
Text("$_n", //_n value is not updating yet
style: TextStyle(fontSize: 60.0)),
FloatingActionButton(
onPressed: minus,
child: Icon(
const IconData(0xe15b, fontFamily: 'MaterialIcons'),
color: Colors.black),
backgroundColor: Colors.white,
),
],
),
),
],
),
),
);
}
);
}
EDIT: In this showDialog document, google say
EDIT2: This code will work
int _n = 0; //counter variable
void add(setState) {
setState(() {
_n++;
});
}
void minus(setState) {
setState(() {
if (_n != 0) _n--;
});
}
void confirmBox() {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(builder: (context, StateSetter setState) {
return Container(
child: Scaffold(
body: Column(
children: <Widget>[
Center(
child: Column(
children: <Widget>[
FloatingActionButton(
onPressed: () => add(setState),
child: Icon(
Icons.add,
color: Colors.black,
),
backgroundColor: Colors.white,
),
Text("$_n", //_n value is not updating yet
style: TextStyle(fontSize: 60.0)),
FloatingActionButton(
onPressed: () => minus(setState),
child: Icon(
const IconData(0xe15b,
fontFamily: 'MaterialIcons'),
color: Colors.black),
backgroundColor: Colors.white,
),
],
),
),
],
),
),
);
});
});
}
Use a StatefulBuilder or a custom StatefulWidget if the dialog needs to update dynamically.
Put this widget and other functions into new StatefulWidget
Container(
child: Scaffold(
body: Column(...
And call it inside builder of showDialog
Wrap all the content of the dialog inside of a StatefulBuilder:
https://api.flutter.dev/flutter/widgets/StatefulBuilder-class.html
Example:
await showDialog<void>(
context: context,
builder: (BuildContext context) {
int selectedRadio = 0;
return AlertDialog(
content: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate(4, (int index) {
return Radio<int>(
value: index,
groupValue: selectedRadio,
onChanged: (int value) {
setState(() => selectedRadio = value);
},
);
}),
);
},
),
);
},
);
There can be two approaches,
Method 1
Just declare a StatefulBuilder or StatefulWidget inside your dialog.
Method 2
Declare a abstract class
abstract class AlertDialogCallback {
void onPositive(Object object);
void onNegative();
}
then implement this class to your widget like this,
class _ContactUsState extends State<ContactUs> implements AlertDialogCallback {
...
//open dialog and pass this to provide callback a context
onPressed:(){CustomAlertDialog(this).openDialog();}
...
//
#override
void onNegative() {
Navigator.of(context).pop();
}
#override
void onPositive(Object object) {
// do your logic here
}
}
Inside CustomAlertDialog get your mAlertDialogCallback and pass a object there
class CustomAlertDialog {
AlertDialogCallback mAlertDialogCallback;
CustomAlertDialog([this.mAlertDialogCallback]);
openDialog() {
// flutter defined function
showDialog(
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: Text(title),
content: Text(message),
actions: <Widget>[
// usually buttons at the bottom of the dialog
FlatButton(
child: Text(
actionButtonText1.toString().toUpperCase(),
),
onPressed: () {
Navigator.of(context).pop();
mAlertDialogCallback.onPositive(obj);
},
)
],
);
},
);
}
}
Create a StatefulWidget with the widgets you need to display in a Dialog
class MyDialog extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyDialogState();
}
}
class _MyDialogState extends State<MyDialog> {
int _n = 0; //counter variable
void add() {
setState(() {
_n++;
});
}
void minus() {
setState(() {
if (_n != 0) _n--;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FloatingActionButton(
onPressed: add,
child: Icon(
Icons.add,
color: Colors.black,
),
backgroundColor: Colors.white,
),
Text("$_n", //_n value is not updating yet
style: TextStyle(fontSize: 60.0)),
FloatingActionButton(
onPressed: minus,
child: Icon(const IconData(0xe15b, fontFamily: 'MaterialIcons'),
color: Colors.black),
backgroundColor: Colors.white,
),
],
),
),
);
}
}
And then make change in your 'confirmBox' method as,
void confirmBox() {
showDialog(
context: context,
builder: (BuildContext context) {
return MyDialog();
},
);
}
In case you want to know if the string only contains the decimal digits you can use this function
bool validateIsInt(String str) {
const String digits = "0123456789";
for (int i = 0; i < str.length; i++) {
bool wasFound = false;
for (int j = 0; j < digits.length; j++) {
if (str[i] == digits[j]) {
wasFound = true;
break;
}
}
if (!wasFound) {
return false;
}
}
return true;
}