Refreshes the page when Slider values ​change - Flutter, BloC - firebase

I'm new to Flutter, I started making a Slider through bloc and writing data to the fireStore, but I ran into a problem - when I change the value in the Slider, the page refreshes and freezes at the Loading stage, this is profile_screen. How can I fix the problem so that the page is not updated in real time, but only by clicking on the Save button?
profile_screen
double _currentSliderValue = 1;
#override
Widget build(BuildContext context) {
// final ProfileBloc infoBloc = context.read<ProfileBloc>();
ProfileCubit cubit = ProfileCubit.get(context);
return Scaffold(
body: Container(
child: Padding(
padding: const EdgeInsets.all(50),
child: BlocBuilder<ProfileCubit, ProfileState>(
builder: (context, state) {
if (state is ProfileInitial) {
return const Center(
child: Text('No Data'),
);
}
if (state is ProfileLoadingState) {
return CircularProgressIndicator();
}
if (state is ProfileLoadedState) {
return FutureBuilder<DocumentSnapshot>(
future: cubit.getInfo(),
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
return _userNoInfo(cubit);
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data =
snapshot.data!.data() as Map<String, dynamic>;
return _userInfo(data, cubit);
}
return Text('Loading');
},
);
}
if (state is ProfileErrorState) {
return Center(
child: Text(state.error.toString()),
);
}
return Container();
})),
));
}
Widget _userInfo(data, ProfileCubit cubit) {
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Form(
key: _formKeyTwo,
child: Column(children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.2,
child: Slider(
value: _currentSliderValue,
max: 100,
divisions: 100,
label: _currentSliderValue.toString(),
onChanged: (value) {
setState(() {
_currentSliderValue = value;
});
}),
),
SizedBox(width: 100),
_cardData(),
],
),
const SizedBox(height: 50.0),
Center(
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.2,
child: ElevatedButton(
onPressed: () {
cubit.addAndUpdateInfo(
_bisNameContr.text,
_bisAddressContr.text,
_contactNameContr.text,
_contactEmailContr.text,
_phoneNumberContr.text,
_progNameContr.text,
_currentSliderValue.toString());
profile_cubit
class ProfileCubit extends Cubit<ProfileState> {
final Database _firestoreRepo;
ProfileCubit(this._firestoreRepo) : super(ProfileInitial()) {
getInfo();
}
static ProfileCubit get(context) => BlocProvider.of(context);
Future<DocumentSnapshot<Object?>> getInfo() {
try {
emit(ProfileLoadingState());
final Future<DocumentSnapshot<Object?>> infoData =
_firestoreRepo.getData();
emit(ProfileLoadedState(infoData));
return infoData;
} catch (e) {
emit(ProfileErrorState(e.toString()));
throw Exception(e);
}
}
Future<void> addAndUpdateInfo(
final String bisName,
final String bisAddress,
final String contactName,
final String contactEmail,
final String phoneNumber,
final String progName,
final String progYears,
) async {
await _firestoreRepo.addAndUpdateInfo(bisName, bisAddress, contactName,
contactEmail, phoneNumber, progName, progYears);
}
}
cubit_state
abstract class ProfileState extends Equatable {
const ProfileState();
#override
List<Object> get props => [];
}
class ProfileInitial extends ProfileState {}
class ProfileLoadingState extends ProfileState {}
class ProfileLoadedState extends ProfileState {
final Future<DocumentSnapshot<Object?>> dataInfo;
const ProfileLoadedState(this.dataInfo);
}
class ProfileErrorState extends ProfileState {
final String error;
const ProfileErrorState(this.error);
#override
List<Object> get props => [error];
}

If you don't want to build everytime, you can use buildWhen which is available inside BlocBuilder. if the value is true, it will rebuild everytime and if it's false, it will never rebuild. So you can keep a condition over there based on your requirements.

Related

How do I write to firebase using a TextFormField in a ListView in flutter

Hi Im trying to use List views to make the UI more interactive and appealing, however I cant figure out a way to pass the TextEditingController() pass through to other pages. I've attached the relevant pages of code below.
admin_exercise.dart
this creates both list views this is then passed to admin_screen.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
final _firestore = Firestore.instance;
late FirebaseUser users;
class ExerciseView extends StatefulWidget {
const ExerciseView({Key? key}) : super(key: key);
#override
State<ExerciseView> createState() => _ExerciseViewState();
}
class _ExerciseViewState extends State<ExerciseView> {
void getUsers() {
users.email.toString();
}
var selectedUser;
bool setDefaultUser = true;
var selectedExercise;
bool setDefaultExercise = true;
int set1 = 0;
List<Widget> _cardList = [];
void _addCardWidget() {
setState(() {
_cardList.add(SetCard(
setNo: (set1 + 1),
exercise: selectedExercise,
));
});
}
void _deleteCardWidget() {
setState(() {
_cardList.removeLast();
});
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Center(
child: StreamBuilder<QuerySnapshot>(
stream: _firestore
.collection('exercises')
.orderBy('Title')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return Container();
if (setDefaultExercise) {
selectedExercise = snapshot.data?.documents[0]['Title'];
}
return DropdownButton(
isExpanded: false,
value: selectedExercise,
items: snapshot.data?.documents.map((value2) {
return DropdownMenuItem(
value: value2['Title'],
child: Text('${value2['Title']}'),
);
}).toList(),
onChanged: (value2) {
setState(
() {
// Selected value will be stored
selectedExercise = value2;
// Default dropdown value won't be displayed anymore
setDefaultExercise = false;
},
);
},
);
},
),
),
ElevatedButton(
onPressed: () {
setState(() {
_addCardWidget();
set1++;
});
},
child: Icon(Icons.add)),
Text('Sets: $set1'),
ElevatedButton(
onPressed: () {
_deleteCardWidget();
setState(() {
set1--;
});
},
child: Icon(Icons.remove))
],
),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: _cardList.length,
itemBuilder: (context, index) {
return _cardList[index];
}),
],
);
}
}
class SetCard extends StatelessWidget {
SetCard({required this.setNo, required this.exercise});
late int setNo;
late String exercise;
final repTextController = TextEditingController();
final notesTextController = TextEditingController();
Future<void> insertData(final reps) async {
_firestore.collection("plans").add(reps).then((DocumentReference document) {
print(document.documentID);
}).catchError((e) {
print(e);
});
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
// added in exercise here to help to understand how to write to firebase
Text('Set $setNo, $exercise'),
Row(
children: [
Expanded(
flex: 1,
child: TextFormField(
controller: repTextController,
decoration: InputDecoration(hintText: 'Reps...'),
keyboardType: TextInputType.number,
),
),
SizedBox(
width: 10,
),
Expanded(
flex: 4,
child: TextFormField(
controller: notesTextController,
decoration: InputDecoration(hintText: 'Notes...'),
keyboardType: TextInputType.number,
),
),
],
),
],
);
}
}
admin_screen.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:intl/intl.dart';
import 'package:fitness_guide/components/admin_exercise.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class AdminScreen extends StatefulWidget {
static const String id = 'AdminScreen';
const AdminScreen({Key? key}) : super(key: key);
#override
State<AdminScreen> createState() => _AdminScreenState();
}
class _AdminScreenState extends State<AdminScreen> {
TextEditingController dateinput = TextEditingController();
final _auth = FirebaseAuth.instance;
final _firestore = Firestore.instance;
late FirebaseUser users;
void getUsers() {
users.email.toString();
}
var selectedUser;
bool setDefaultUser = true;
var selectedExercise;
bool setDefaultExercise = true;
int set1 = 0;
#override
void initState() {
dateinput.text = ""; //set the initial value of text field
super.initState();
}
List<Widget> _exerciseList = [];
void _addExerciseWidget() {
setState(() {
_exerciseList.add(ExerciseView());
});
}
void _deleteExerciseWidget() {
setState(() {
_exerciseList.removeLast();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
//submit to fbase here
},
label: const Text('Submit'),
icon: const Icon(Icons.thumb_up),
backgroundColor: Color(0xff75D6F2),
),
body: ListView(
physics: BouncingScrollPhysics(),
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
height: 20,
),
Container(
padding: EdgeInsets.symmetric(horizontal: 15),
height: 50,
child: Center(
child: TextField(
controller:
dateinput, //editing controller of this TextField
decoration: InputDecoration(
icon: Icon(Icons.calendar_today), //icon of text field
labelText: "Enter Date" //label text of field
),
readOnly:
true, //set it true, so that user will not able to edit text
onTap: () async {
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(
2000), //DateTime.now() - not to allow to choose before today.
lastDate: DateTime(2101));
if (pickedDate != null) {
print(
pickedDate); //pickedDate output format => 2021-03-10 00:00:00.000
String formattedDate =
DateFormat('yyyy-MM-dd').format(pickedDate);
print(
formattedDate); //formatted date output using intl package => 2021-03-16
//you can implement different kind of Date Format here according to your requirement
setState(() {
dateinput.text =
formattedDate; //set output date to TextField value.
});
} else {
print("Date is not selected");
}
},
))),
Center(
child: StreamBuilder<QuerySnapshot>(
stream: _firestore
.collection('users')
.orderBy('email')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
// Safety check to ensure that snapshot contains data
// without this safety check, StreamBuilder dirty state warnings will be thrown
if (!snapshot.hasData) return Container();
// Set this value for default,
// setDefault will change if an item was selected
// First item from the List will be displayed
if (setDefaultUser) {
selectedUser = snapshot.data?.documents[0]['email'];
}
return DropdownButton(
isExpanded: false,
value: selectedUser,
items: snapshot.data?.documents.map((value1) {
return DropdownMenuItem(
value: value1['email'],
child: Text('${value1['email']}'),
);
}).toList(),
onChanged: (value1) {
setState(
() {
// Selected value will be stored
selectedUser = value1;
// Default dropdown value won't be displayed anymore
setDefaultUser = false;
},
);
},
);
},
),
),
//Row below for the first exercise sets etc
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: _exerciseList.length,
itemBuilder: (context, index) {
return _exerciseList[index];
}),
Center(
child: Text('Add another Exercise...'),
),
ElevatedButton(
onPressed: () {
_addExerciseWidget();
},
child: Icon(Icons.add),
),
],
),
],
),
);
}
}```
p.s. sorry for any spagetti code
[Image shows the layout UI I'm trying to achieve][1]
[1]: https://i.stack.imgur.com/d3qxg.png

Flutter : Autorun a function whenever a screen is opened

I want to run a function to check Firestore data everytime a screen is loaded.
Here is my code :
class PlaceTile extends StatefulWidget {
//String flag='inactive';
final Place place;
PlaceTile({ this.place });
#override
_PlaceTileState createState() => _PlaceTileState(place);
}
class _PlaceTileState extends State<PlaceTile> {
final Place place;
_PlaceTileState(this.place);
String flag = 'inactive';
void getUserById(String id) {
DatabaseService().placesCollection.document(id).get().then((DocumentSnapshot doc) {
print(doc.data);
});
}
checkUserStatus() async {
final FirebaseAuth auth = FirebaseAuth.instance;
final FirebaseUser user = await auth.currentUser();
String uid = user.uid;
UserDatabaseService().userCollection.getDocuments().then((QuerySnapshot snapshot) {
snapshot.documents.forEach((DocumentSnapshot doc) {
if(doc.documentID == uid)
{
if(doc.data['status']=='true')
{
setState(() {
flag = 'active';
});
}
else
{
setState(() {
flag = 'inactive';
});
}
}
});
});
return flag;
}
void showQueueDetailsPanel() {
showModalBottomSheet(context: context, builder: (context) {
return Container(
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 60.0),
child: QueueDetails(value: place.name),
);
});
}
String value;
#override
initState() {
super.initState();
checkUserStatus();
}
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(top: 8.0),
child: Card(
margin: EdgeInsets.fromLTRB(20.0, 6.0, 20.0, 0.0),
child: ListTile(
isThreeLine: true,
leading: CircleAvatar(
radius: 25.0,
backgroundColor: Colors.white,
),
title: Text(place.name),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
//Text("People "+place.totalPeople.toString()),
Text(""),
Text("Token "+place.tokenAvailable.toString()),
],
),
trailing: FlatButton.icon(
icon: Icon(Icons.add_box_rounded),
label: Text('Join'),
onPressed: checkUserStatus() =='inactive' ? showQueueDetailsPanel : null,
),
),
)
);
}
}
The code in its current state doesn't perform the way I expect. It s a fallacy actually. Once flag is set to active, the Join buttons get disabled and for them to get re-enabled they need to be pressed for condition to be checked. But that's not possible since the buttons can't be pressed as they are disabled.
I want to run checkUserStatus() everytime this page with PlaceTile is loaded. How can I achieve that? A code snippet would be helpful.
Add your function to initState:
#override
void initState() {
super.initState();
checkUserStatus();
}
Widget build(BuildContext context) {
return FutureBuilder(
future: futurePackageListModel,
builder: (BuildContext context,
AsyncSnapshot<List<Model>> snapshot) {
if (snapshot.hasData) {
return YourWidget();
} else if (snapshot.hasError) {
return ApiStatusFail();
} else {
return Center(child: CircularProgressIndicator());
}
},
);
}

Getting ExpansionPanelList to work inside Streambuilder in flutter

I'm trying to arrange some data streamed from Firebase with an ExpansionPanellist. The panelList is placed inside a StreamBuilder, and above the StreamBuilder i have a SingleChildScrollView.
I am able to get the list showing with the headers, but i can't get the expand/collapse function to work, so I am not able to see the body-text.
screenshot of the list
The expanding/collapinsg function worked outside the Streambuilder, but I was not able to access the data from Firebase then.
Any help will be much appreciated! If this is the wrong way of doing this, I will also be grateful for any pointers to alternative ways of achieving this. (There won't be any data added to the server while looking at past climbs and graphs, so a streambuilder might not be necessary if there are easier/better ways).
-Kristian
class Graphs extends StatefulWidget {
static String id = 'graphs_screen';
#override
_GraphsState createState() => _GraphsState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(tabs: [
Tab(text: 'Graphs'),
Tab(text: 'Stats'),
Tab(text: 'Climbs'),
]),
),
body: TabBarView(
children: [
//Image.asset('assets/images/line_graph.png'),
Expanded(child: NumericComboLinePointChart.withSampleData()),
Container(
child: Text(''),
),
SingleChildScrollView(
child: DataStream(),
),
],
)),
),
);
}
}
class DataStream extends StatefulWidget {
#override
_DataStreamState createState() => _DataStreamState();
}
class _DataStreamState extends State<DataStream> {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _firestore
.collection('climbs')
.orderBy('Date', descending: true)
.snapshots(),
builder: (context, snapshot) {
List<ExpansionItem> expansionList = <ExpansionItem>[];
if (snapshot.hasData) {
final alldata = snapshot.data.docs;
for (var data in alldata) {
final dataFunction = data.data();
final grades = dataFunction['gradeScore'];
final climbDate = dataFunction['Date'];
final climbDateT = DateTime.fromMicrosecondsSinceEpoch(
climbDate.microsecondsSinceEpoch);
String climbDateString =
"${climbDateT.year.toString()}-${climbDateT.month.toString().padLeft(2, '0')}-${climbDateT.day.toString().padLeft(2, '0')} ${climbDateT.hour.toString()}-${climbDateT.minute.toString()}";
final climber = dataFunction['sender'];
final currentUSer = loggedInUser.email;
if (climber == loggedInUser.email) {
expansionList.add(ExpansionItem(
dateTimeHeader: climbDateString,
climbs: grades.toString()));
}
}
}
return ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
print('tap registered');
expansionList[index].isExpanded = !isExpanded;
});
},
children: expansionList.map((ExpansionItem item) {
return ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return Container(
child: Text(item.dateTimeHeader),
);
},
body: Container(
child: Text(item.climbs),
),
isExpanded: item.isExpanded,
);
}).toList(),
);
});
}
}
class ExpansionItem {
ExpansionItem({this.isExpanded: false, this.dateTimeHeader, this.climbs});
bool isExpanded;
final String dateTimeHeader;
final String climbs;
}
I also ran into this issue and managed to develop a "work-around" (sorry in advance, it's a bit messy).
The reason your expansion tiles are not expanding is due to the nature of expansionCallback function. Once you press the expand button it also causes your StreamBuilder to rebuild. Therefore, since you're initializing "expansionList" within the StreamBuilder it will reset "isExpanded" back to false no matter how many times you press it. So your best option is to initialize the expansionList outside of the StreamBuilder and modify it from within. Check below for my solution but I welcome anyone to optimize it and/or share a better one.
class ExpansionItem {
String headerValue;
bool isExpanded;
SplitObject item;
ExpansionItem({this.item, this.headerValue, this.isExpanded = false});
}
class MyExample extends StatefulWidget{
#override
_MyExampleState createState() => _MyExampleState();
}
class _MyExampleState extends State<MyExample> {
//Pick a number as large as you see fit to always be more than necessary.
List<ExpansionItem> expansionItems = List<ExpansionItem>.generate('anyNumber', (int index)=> ExpansionItem(isExpanded: false,));
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Scaffold(
appBar: AppBar(
title: Text('Split',style: Theme.of(context).textTheme.headline3,),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(8),
child: Container(
child: StreamBuilder(
builder: (context, streamData){
if(streamData.hasData){
List<SplitObject> items = streamData.data;
//Save data to Expansion list by iterating through it.
for (var i = 0; i < items.length; i++){
try {
expansionItems[i].item =items[i];
expansionItems[i].headerValue =items[i].itemName;
} catch (e) {
// Catch any range errors after trimming list.
if(e.toString().contains('RangeError')) {
expansionItems.add(ExpansionItem(
item: items[i], headerValue: items[i].itemName));
}
}
}
// Trim list
expansionItems = expansionItems.getRange(0, items.length).toList();
return _buildListPanel(expansionItems);
} else {
return ListTile(
title: Text('No items to split.'),
);
}
},
stream: DatabaseService().splitItemData,
),
),
),
)
);
}
Widget _buildListPanel(List<ExpansionItem> expansionItems){
// print(expansionItems[0].isExpanded);
return ExpansionPanelList(
expansionCallback: (int index, bool isExpanded){
setState(() {
expansionItems[index].isExpanded = !isExpanded;
// print(expansionItems[index].isExpanded);
});
},
children: expansionItems.map<ExpansionPanel>((ExpansionItem item){
return ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded){
print(item.isExpanded);
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(),
);
},
body: Container(),
isExpanded: item.isExpanded,
);
}).toList(),
);
}
}
You should create a class where your Expansion List will be, then your Stream builder must call it. Doing it this way Expansion Panel List callback will function just normal.
Look:
class _ExpansionPanelClass extends State<ExpansionPanelClass> {
#override
Widget build(BuildContext context) {
return ExpansionPanelList(
elevation: 3,
expansionCallback: (index, isExpanded) {
setState(() {
widget.product[index]['isExpanded'] = !isExpanded;
});
},
animationDuration: const Duration(milliseconds: 600),
children: widget.product
.map(
(item) => ExpansionPanel(
canTapOnHeader: true,
backgroundColor:
item['isExpanded'] == true ? Colors.cyan[100] : Colors.white,
headerBuilder: (_, isExpanded) => Container(
padding:
const EdgeInsets.symmetric(vertical: 15, horizontal: 30),
child: Text(
item['title'],
style: const TextStyle(fontSize: 20),
)),
body: Container(
padding:
const EdgeInsets.symmetric(vertical: 15, horizontal: 30),
child: Text(item['description']),
),
isExpanded: item['isExpanded'],
),
)
.toList(),
);
}
}
Then from your StreamBuilder:
StreamBuilder<QuerySnapshot>(
stream: dbProducts
.collection(ids[i])
.orderBy('order', descending: false)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Map<String, dynamic>> _items = [];
for (var document in snapshot.data!.docs) {
Map data = Map.from(document.data() as Map);
if (data['hide'] == false) {
Map<String, dynamic> map = {
'id': _items.length,
'title': data['name'],
'description': data['ingredients'],
'isExpanded': false
};
_items.add(map);
}
}
return ExpansionPanelClass(product: _items);
} else {
return const Center(
child: CircularProgressIndicator(
color: Colors.brown,
),
);
}
},
),
That's all.

How do you update a FutureBuilder when the Sqlite db it gets data from gets new data?

I am using a FutureBuilder to display data from a sqlite database. I can add data to that database using a drawer that is also on that page. When I add new data I want the FutureBuilder to automatically update so the page will display the new data that was just added to the sqlite database. How could I properly go about doing this? Thanks!
The page where the data is displayed using the FutureBuilder
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[200],
appBar: PreferredSize(
preferredSize: Size.fromHeight(95.0),
child: AppBar(
automaticallyImplyLeading: false, // hides leading widget
flexibleSpace: DataAppBar(),
),
),
body: FutureBuilder<dynamic>(
future: DataDBProvider.dataDB.getData(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('none');
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.active:
return Text('');
case ConnectionState.done:
if (snapshot.hasError) {
print(
'${snapshot.error}',
);
}
}
List data = snapshot.data;
return ListView.builder(
itemCount: data.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 1.0, horizontal: 4.0),
child: Card(
color: (index % 2 == 0) ? greycolor : Colors.white,
child: Container(
height: 60,
padding: EdgeInsets.fromLTRB(0, 20, 0, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Flexible(
flex: 3,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(left: 4),
child: Text(data[index].date,
style: TextStyle(fontSize: 14),
textAlign: TextAlign.left),
),
],
),
),
Expanded(
flex: 5,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(data[index].title,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
fontFamily: 'Montserrat'),
textAlign: TextAlign.center)
],
)),
],
),),
Expanded(
flex: 3,
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('\$${data[index].amount}',
style: TextStyle(fontSize: 17,
color: Colors.black),
textAlign: TextAlign.right),
],
),
),
],
)),
),
);
},
);
}
));
}
You can copy paste run full code below
To achieve automatically update so the page will display the new data
In this case, you can use package https://pub.dev/packages/sqlbrite and StreamBuilder
sqlbrite is Streaming sqflite, The BriteDatabase.createQuery method is similar to Database.query. Listen to the returned Stream<Query> which will immediately notify with a Query to run.
And the page will automatically display the new data
code snippet
class AppDb {
...
final _dbFuture = _open().then((db) => BriteDatabase(db));
Stream<List<Item>> getAllItems() async* {
final db = await _dbFuture;
yield* db
.createQuery(_tableItems, orderBy: 'createdAt DESC')
.mapToList((json) => Item.fromJson(json));
}
Future<bool> insert(Item item) async {
final db = await _dbFuture;
final id = await db.insert(
_tableItems,
item.toJson(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
return id != -1;
}
Future<bool> remove(Item item) async {
...
}
Future<bool> update(Item item) async {
...
}
}
...
StreamBuilder<List<Item>>(
stream: AppDb.getInstance().getAllItems(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
final items = snapshot.data;
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
working demo
full code
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:sqlbrite/sqlbrite.dart';
import 'dart:math';
const _tableItems = 'items';
Future<Database> _open() async {
final directory = await getApplicationDocumentsDirectory();
final path = join(directory.path, 'example.db');
return await openDatabase(
path,
version: 1,
onCreate: (Database db, int version) async {
await db.execute(
'''
CREATE TABLE $_tableItems(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
content TEXT NOT NULL,
createdAt TEXT NOT NULL
)
''',
);
final batch = db.batch();
for (int i = 0; i < 10; i++) {
batch.insert(
_tableItems,
Item(
null,
contents.random(),
DateTime.now(),
).toJson(),
);
}
final list = await batch.commit(
continueOnError: true,
noResult: false,
);
print('Batch result: $list');
},
);
}
class AppDb {
static AppDb _singleton;
AppDb._();
factory AppDb.getInstance() => _singleton ??= AppDb._();
final _dbFuture = _open().then((db) => BriteDatabase(db));
Stream<List<Item>> getAllItems() async* {
final db = await _dbFuture;
yield* db
.createQuery(_tableItems, orderBy: 'createdAt DESC')
.mapToList((json) => Item.fromJson(json));
}
Future<bool> insert(Item item) async {
final db = await _dbFuture;
final id = await db.insert(
_tableItems,
item.toJson(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
return id != -1;
}
Future<bool> remove(Item item) async {
final db = await _dbFuture;
final rows = await db.delete(
_tableItems,
where: 'id = ?',
whereArgs: [item.id],
);
return rows > 0;
}
Future<bool> update(Item item) async {
final db = await _dbFuture;
final rows = await db.update(
_tableItems,
item.toJson(),
where: 'id = ?',
whereArgs: [item.id],
conflictAlgorithm: ConflictAlgorithm.replace,
);
return rows > 0;
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData.dark(),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
final _dateFormatter = DateFormat.Hms().add_yMMMd();
MyHomePage({Key key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('sqlbrite example'),
),
body: Container(
constraints: BoxConstraints.expand(),
child: StreamBuilder<List<Item>>(
stream: AppDb.getInstance().getAllItems(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
final items = snapshot.data;
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
title: Text(item.content),
subtitle:
Text('Created: ${_dateFormatter.format(item.createdAt)}'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.remove_circle),
onPressed: () => _remove(item),
),
IconButton(
icon: Icon(Icons.edit),
onPressed: () => _update(item),
),
],
),
);
},
);
},
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: _add,
),
);
}
void _add() async {
final item = Item(
null,
contents.random(),
DateTime.now(),
);
final success = await AppDb.getInstance().insert(item);
print('Add: $success');
}
void _remove(Item item) async {
final success = await AppDb.getInstance().remove(item);
print('Remove: $success');
}
void _update(Item item) async {
final success = await AppDb.getInstance().update(
item.copyWith(
contents.random(),
),
);
print('Update: $success');
}
}
const contents = [
'Aaren',
'Aarika',
'Abagael',
'Abagail',
'Abbe',
'Abbey',
'Abbi',
'Abbie',
'Abby',
'Abbye',
'Abigael',
'Abigail',
'Abigale',
'Abra',
'Ada',
'Adah',
'Adaline',
'Adan',
'Adara',
'Adda',
'Addi',
'Addia',
'Addie',
'Addy',
'Adel',
'Adela',
'Adelaida',
'Adelaide',
'Adele',
'Adelheid',
'Adelice',
'Adelina',
'Adelind',
'Adeline',
'Adella',
'Adelle',
'Adena',
'Adey',
'Adi',
'Adiana',
'Adina',
'Adora',
'Adore',
'Adoree',
'Adorne',
'Adrea',
'Adria',
'Adriaens',
'Adrian',
'Adriana',
'Adriane',
'Adrianna',
'Adrianne',
'Adriena',
'Adrienne',
'Aeriel',
'Aeriela',
'Aeriell',
'Afton',
'Ag',
'Agace',
'Agata',
'Agatha',
'Agathe',
'Aggi',
'Aggie',
'Aggy',
'Agna',
'Agnella',
'Agnes',
'Agnes',
];
extension RandomElementExtension<T> on List<T> {
T random() {
final index = Random().nextInt(length);
return this[index];
}
}
class Item {
final int id;
final String content;
final DateTime createdAt;
const Item(
this.id,
this.content,
this.createdAt,
);
factory Item.fromJson(Map<String, dynamic> json) {
return Item(
json['id'],
json['content'],
DateTime.parse(json['createdAt']),
);
}
Map<String, dynamic> toJson() {
return {
if (id != null) 'id': id,
'content': content,
'createdAt': createdAt.toIso8601String(),
};
}
Item copyWith(String content) => Item(id, content, createdAt);
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is Item &&
runtimeType == other.runtimeType &&
id == other.id &&
content == other.content &&
createdAt == other.createdAt;
#override
int get hashCode => id.hashCode ^ content.hashCode ^ createdAt.hashCode;
#override
String toString() =>
'Item{id: $id, content: $content, createdAt: $createdAt}';
}

Dependencies between models in flutter

Help me please. There is a database that contains several tables:
1) The names of the authors of poems.
2) The names of poems.
One table is linked to another using an ID (in the table with the names of the poems there is a column with the author ID).
It is necessary that by clicking on the author poems of this author open. I can’t figure out how to make this connection.
Authors.dart (The models of authors)
Authors authorsFromJson(String str) {
final jsonData = json.decode(str);
return Authors.fromMap(jsonData);
}
String authorsToJson(Authors data) {
final dyn = data.toMap();
return json.encode(dyn);
}
class Authors {
int id;
String name;
int count;
Authors({
this.id,
this.name,
this.count,
});
factory Authors.fromMap(Map<String, dynamic> json) => new Authors(
id: json["c_id"],
name: json["c_title"],
count: json["c_i_count"],
);
Map<String, dynamic> toMap() => {
"c_id": id,
"c_title": name,
"c_i_count": count,
};
}
PoemsTitle.dart (The models of PoemsTitle)
PoemsTitle poemsTitleFromJson(String str) {
final jsonData = json.decode(str);
return PoemsTitle.fromMap(jsonData);
}
String poemsTitleToJson(PoemsTitle data) {
final dyn = data.toMap();
return json.encode(dyn);
}
class PoemsTitle {
int id;
String title;
int authorsId;
PoemsTitle({
this.id,
this.title,
this.authorsId,
});
factory PoemsTitle.fromMap(Map<String, dynamic> json) => new PoemsTitle(
id: json["ii_i_id"],
title: json["ii_i_title"],
authorsId: json["ii_col_int_2"],
);
Map<String, dynamic> toMap() => {
"ii_i_id": id,
"ii_i_title": title,
"ii_col_int_2": authorsId,
};
}
Column c_title from the category table must be associated with column ii_col_int_2 from the items_info table.
Methods from DBProvider:
...Future<List<Authors>> getAllAuthors() async {
final db = await database;
var res = await db.query('category');
List<Authors> list = res.map((c) => Authors.fromMap(c)).toList();
return list;
}
Future<List<PoemsTitle>> getAllPoemsTitle() async {
final db = await database;
var res = await db.query('items_info');
List<PoemsTitle> list = res.map((c) => PoemsTitle.fromMap(c)).toList();
return list;
}
And pages from FutureBuilder and ListView.builder:
This is a widget where poems are displayed after clicking on the corresponding author:
class FavoriteTab extends StatelessWidget {
int authorId;
int count;
FavoriteTab({this.authorId, this.count});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.only(left: 10, right: 10, bottom: 10),
child: Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
color: Theme.of(context).accentColor,
borderRadius: BorderRadius.all(Radius.circular(30))
),
child: FutureBuilder<List<PoemsTitle>>(
future: DBProvider.db.getAllPoemsTitle(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
case ConnectionState.done:
{
if (snapshot.hasError) {
return Center(
child: Text(snapshot.error.toString()),
);
} else if (snapshot.hasData) {
return Scrollbar(
child: ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: this.count,
itemBuilder: (context, index) {
PoemsTitle item = snapshot.data[index];
return ListTile(
title: Text(item.title),
leading: Icon(Icons.receipt),
trailing: Icon(Icons.favorite, color: Colors.red, size: 34),
onTap: () {
},
);
},
)
);
}
return Center(
child: Text('No Data')
);
}
default:
return Container();
}
},
)
),
),
);
}
}
And this is the Widget with the authors:
class MainTab extends StatefulWidget {
#override
_MainTabState createState() => _MainTabState();
}
class _MainTabState extends State<MainTab> {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 10, right: 10, bottom: 10),
child: Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
color: Theme.of(context).accentColor,
borderRadius: BorderRadius.all(Radius.circular(30))
),
child: FutureBuilder<List<Authors>>(
future: DBProvider.db.getAllAuthors(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
case ConnectionState.done:
{
if (snapshot.hasError) {
return Center(
child: Text(snapshot.error.toString()),
);
} else if (snapshot.hasData) {
return Scrollbar(
child: ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
Authors item = snapshot.data[index];
return ListTile(
title: Text(item.name),
leading: Icon(Icons.folder),
trailing: Text(item.count.toString()),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FavoriteTab(authorId: item.id, count: item.count)
)
);
},
);
},
)
);
}
return Center(
child: Text('No Data')
);
}
default:
return Container();
}
},
)
),
);
}
}
I think the problem is on this line, in the FavoriteTab widget:
future: DBProvider.db.getAllPoemsTitle(),
Here you ALWAYS query all poems. The right thing is to create a method like getPoemOfAuthor(int authorID) in DBProvider, and since you pass the authorId when you open your FavoriteTab, in the FutureBuilder you must call:
future: DBProvider.db.getPoemOfAuthor(authorId),
Hope this will help!

Resources