I'm making a text game with a sf fiction writer.
This game is similar to the Japanese visual novel. The story changes through choice.
DB is using sqflite.
I am loading text via listview.builder.
The list with dialogue is named Notes. I made it with reference to the memo app and did not edit it.
But I don't want to load the index sequentially.
I thought listview.builder was a for loop. Something is different. I want to change idex but it is difficult for me.
I would like to see 4,5 when it is index 2. index 6 and 7 should not be visible.
When index 3, index 4,5 should not be visible. I wish I could jump to index 6 or 7.
Actual index 2 and 3 are buttons.
I had to post a question in a hurry, so I made a new one. Can you give me a hint?
I also upload a table I made randomly.
Even if it is different from the intention of my question, please suggest a good direction.
It's ok if it's different from my code.
Not using DB, not using sqlite, dividing DB in two, using for loop….
text_game_viewer
dialogue_db.csv
Widget Builder() {
return FutureBuilder(
builder: (context, Snap) {
if ((Snap.data as List).length == 0) {
return Container(
child: Text("add a note"),
);
}
return ListView.builder(
itemCount: (Snap.data as List).length,
itemBuilder: (context, index) {
Memo memo = (Snap.data as List)[index];
getNextItem(index) {
return Column(
children: <Widget>[
Text(
memo.title,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(memo.text),
Padding(padding: EdgeInsets.only(bottom: 20))
],
);
}
return getNextItem(index);
},
);
},
future: loadMemo(),
);}
I don't know if this could help you but there is a lot of solution to do that, what I can share is:
you can use Graph approach using a simple chained object list (more complex)
you can transform the dialog_db.csv to list of object.
simple demonstration that I haven't test using the csv transformation. You need to adapt the code to your needs
For eg:
class MyDialog {
final int id;
final String type;
final String char;
final String text;
final List<int> nextIds; //min element count must be 1
MyDialog(this.id, this.char, this.type, this.text, this.nextIds);
#override
String toString() {
return "$id, $char, $type, $text";
}
}
data example
var dialog = [
MyDialog(1, "Toto", "line", "First line message", [2]),
MyDialog(2, "Toto", "choice", "Yes or no?", [4,5]),
MyDialog(3, "Toto", "line", "must be hidden", [100]),
MyDialog(4, "Toto", "choice", "YES", [7]),
MyDialog(5, "Toto", "choice", "NO", [8]),
//...
];
implementation example:
void test() {
//show the dialog 1
showDialog(1);
//then 2 and his next dialog, the dialog 3 must be skiped
showDialog(2);
}
MyDialog getDialog(int id) {
return dialog.where((element) => element.id == id).first;
}
void showDialog(int id) {
var d = getDialog(id);
print(d);
for (var i in d.nextIds) {
print("next: " + getDialog(i).toString());
}
}
Related
Basically, I have a set of tags done as an array in firebase and want to show them as string in flutter. Is this possible? I'm completely lost here.
I've gotten this far: but I'm not sure I understand what I'm doing here and it doesn't seem to work
class Tags {
List<dynamic>? selectedItems;
Tags fromMap(Map<String, dynamic> map) {
selectedItems =
(map[selectedItems] as List).map((item) => item as String).toList();
return this;
}
}
class TagsList extends StatelessWidget {
const TagsList({super.key});
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: 1,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
child: Center(child: Text('${Tags}')),
);
});
}
}
I hope that I understood your question right , You want to render the items that you got from firebase in your screen? if yes then here is a code snippet .
void getDataBaseCarouselData() async {
final data = await _firestore.collection("Carousels").get();
carouselItems = [];
for (var item in data.docs) {
carouselItems.add(CarouselItem(
title: item.data()["title"],
description: item.data()["description"],
imageUrl: item.data()["imageUrl"],
id: item.id));
}
notifyListeners();
}
.get() return a Map that you can use to get data from Objects using the tags name ["field name in firebase"] and then you can use the List of object to render them into your screen .
If I didn't answer it please provide more information so I can get it clear . Thank you
I am integrating a chat feature in my mobile application, and decided to use Firebase Realtime Database for the backend instad of Firestore as a cost reduction mechanism. I am running into a problem, however. There seems to be very sparse documentation on how to create infinite scrolling using data from Realtime Database instead of Firestore.
Below is the organization of my chat messages. This is the query I want to use:
FirebaseDatabase.instance
.ref("messages/${widget.placeID}")
.orderByChild("timeStamp")
And this is the widget I want to return for each result:
MessageWidget(
message: message.text,
id: message.uid,
name: message.name,
lastSender: message.lastSender,
date: message.timeStamp,
profilePicture: message.profilePicture,
);
Here is the database structure
The query works, and I have already programmed the MessageWidget from the JSON response of the query. All I need is for the query to be called whenever it reaches the top of its scroll, and load more MessageWdigets. Also note, this is a chat app where users are scrolling up, to load older messages, to be added above the previous.
Thank you!
EDIT: here is the code I currently have:
Flexible(
child: StreamBuilder(
stream: FirebaseDatabase.instance
.ref("messages/${widget.placeID}")
.orderByChild("timeStamp")
.limitToLast(20)
.onValue,
builder:
(context, AsyncSnapshot<DatabaseEvent> snapshot) {
if (!snapshot.hasData) {
return const CircularProgressIndicator();
} else {
Map<dynamic, dynamic> map =
snapshot.data!.snapshot.value as dynamic;
List<dynamic> list = [];
list.clear();
list = map.values.toList();
return Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: ListView.builder(
controller: _scrollController,
// shrinkWrap: true,
itemCount: list.length,
itemBuilder: (context, index) {
final json = list[index]
as Map<dynamic, dynamic>;
final message = Message.fromJson(json);
return MessageWidget(
message: message.text,
id: message.uid,
name: message.name,
lastSender: message.lastSender,
date: message.timeStamp,
profilePicture:
message.profilePicture,
);
}),
),
);
}
},
),
),
My initState
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.atEdge) {
bool isTop = _scrollController.position.pixels == 0;
if (isTop) {
//add more messages
} else {
print('At the bottom');
}
}
});
}
Your code already loads all messages.
If you want to load a maximum number of messages, you'll want to put a limit on the number of messages you load. If you want to load only the newest messages, you'd use limitToLast to do so - as the newest messages are last when you order them by their timeStamp value.
So to load for example only the 10 latest messages, you'd use:
FirebaseDatabase.instance
.ref("messages/${widget.placeID}")
.orderByChild("timeStamp")
.limitToLast(10);
This gives you the limited number of messages to initially show in the app.
Now you need to load the 10 previous messages when the scrolling reaches the top of the screen. To do this, you need to know the timeStamp value and the key of the message that is at the top of the screen - so of the oldest message you're showing so far.
With those two values, you can then load the previous 10 with:
FirebaseDatabase.instance
.ref("messages/${widget.placeID}")
.orderByChild("timeStamp")
.endBefore(timeStampValueOfOldestSeenItem, keyOfOldestSeenItem)
.limitToLast(10);
The database here again orders the nodes on their timeStamp, it then finds the node that is at the top of the screen based on the values you give, and it then returns the 10 nodes right before that.
After several days of testing code, I came up with the following solution
The first step is to declare a ScrollController in your state class.
final ScrollController _scrollController = ScrollController();
You will also need to declare a List to store query results
List list = [];
Next, use the following function to get initial data
getStartData() async {
//replace this with your path
DatabaseReference starCountRef =
FirebaseDatabase.instance.ref('messages/${widget.placeID}');
starCountRef
.orderByChild("timeStamp")
//here, I limit my initial query to 6 results, change this to how many
//you want to load initially
.limitToLast(6)
.onChildAdded
.forEach((element) {
setState(() {
list.add(element.snapshot.value);
list.sort((a, b) => a["timeStamp"].compareTo(b["timeStamp"]));
});
});
}
Run this in initState
void initState() {
super.initState();
FirebaseDatabase.instance.setPersistenceEnabled(true);
getStartData();
}
Now to display the initial data that was generated when the page was loaded, build the results into a ListView
ListView.builder(
itemCount: list.length,
controller: _scrollController,
//here I use a premade widget, replace MessageWidget with
//what you want to load for each result
itemBuilder: (_, index) => MessageWidget(
message: list[index]["text"],
date: list[index]["timeStamp"],
id: list[index]["uid"],
profilePicture: list[index]["profilePicture"],
name: list[index]["name"],
lastSender: list[index]["lastSender"],
),
),
Note that your ListView must be constrained, meaning that you can scroll to the beginning or end of your ListView. Sometimes, the ListView won't have enough data to fill and be scrollable, so you must declare a height with a Container or bound it to its contents.
Now you have the code that fetches data when the page is loaded using getStartData() and initState. The data is stored in list, and a ListView.builder builds a MessageWidget for each item returned by getStartData. Now, you want to load more information when the user scrolls to the top.
getMoreData() async {
var moreSnapshots = await FirebaseDatabase.instance
.ref("messages/${widget.placeID}")
.orderByChild("timeStamp")
.endBefore(list[0]["timeStamp"])
.limitToLast(20)
.once();
var moreMap = moreSnapshots.snapshot.value as dynamic;
setState(() {
list.addAll(moreMap.values.toList());
list.sort((a, b) => a["timeStamp"].compareTo(b["timeStamp"]));
});
}
Then, make the function run when the ListView.builder is scrolled all the way to the top by adding this to the already existing initState.
_scrollController.addListener(() {
if (_scrollController.position.atEdge) {
bool isTop = _scrollController.position.pixels == 0;
if (isTop) {
getMoreData();
}
}
});
Hopefully this helps or gives you a pointer on where to go from here. Thanks to Frank van Puffelen for his help on which query to use based on the previous answer.
I am having trouble deleting Maps in a data table in Firestore. Indeed, either I delete my entire array, or I receive an error of the type:
flutter: Failed to delete 1: Invalid argument: Instance of '_CompactLinkedHashSet '
I am attaching my classes to you so that you can understand better.Thank you in advance
CLASS Delete_description :
import 'package:cloud_firestore/cloud_firestore.dart';
class DeleteDescription {
final String city;
final String citee;
final int value;
CollectionReference cities = FirebaseFirestore.instance.collection('city');
DeleteDescription(this.city, this.citee, this.value) {
deleteDescription();
}
Future<void> deleteDescription() {
return cities
.doc(city)
.collection("citee")
.doc(citee)
.set({
"Description": FieldValue.arrayRemove([
{0}
])
})
.then((value) => print("$citee Deleted"))
.catchError((error) => print("Failed to delete $value: $error"));
}
}
CLASS READDESCRIPTION:
import 'package:ampc_93/fonction/firebase_crud/delete_description.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class ReadDescription extends StatefulWidget {
final String titreCity;
final String titreCitee;
ReadDescription(this.titreCity, this.titreCitee);
#override
_ReadDescriptionState createState() => _ReadDescriptionState();
}
class _ReadDescriptionState extends State<ReadDescription> {
#override
Widget build(BuildContext context) {
CollectionReference cities = FirebaseFirestore.instance.collection("city");
return FutureBuilder<DocumentSnapshot>(
future: cities
.doc(widget.titreCity)
.collection("citee")
.doc(widget.titreCitee)
.get(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
return Text("Documents does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data!.data() as Map<String, dynamic>;
if (data["Description"] == null) {
return Text("");
} else {
return ListView.separated(
itemBuilder: (context, index) {
return ListTile(
title: Text(
data["Description"][index]["Identite"],
textAlign: TextAlign.justify,
),
subtitle: Text(
data["Description"][index]["Role"],
textAlign: TextAlign.justify,
style: TextStyle(
decoration: TextDecoration.underline,
color: Colors.red),
),
leading: Icon(Icons.person),
trailing: IconButton(
onPressed: () => DeleteDescription(
widget.titreCity, widget.titreCitee, index),
icon: Icon(Icons.delete_forever),
color: Colors.red[300],
));
},
separatorBuilder: (context, index) => Divider(),
itemCount: data["Description"].length);
}
}
return Text("Loading");
},
);
}
}
I specify that in my database, "Description" is an array and that I would therefore like to delete all the elements of "Description" number 0 for example
The FieldValue.arrayRemove you are using didn't work in this way. There are two methods to delete data from firestore list.
First way is pass element (Not it's index) in FieldValue.arrayRemove which you wants to delete.
Second way is get collection from firestore and modify data according to your need and update collection in firestore.
Have a look on below code for more understanding.
import 'package:cloud_firestore/cloud_firestore.dart';
class DeleteDescription {
final String city;
final String citee;
final int value;
CollectionReference cities = FirebaseFirestore.instance.collection('city');
DeleteDescription(this.city, this.citee, this.value) {
deleteDescription();
}
Future<void> deleteDescription() {
final snapshot = await cities.doc(city).collection("citee").doc(citee).get();
/* Get list from firestore */
final list = snapshot["Description"] as List;
/* Remove first or any element and delete from list */
list.removeAt(0);
/* Update same list in firestore*/
await cities
.doc(city)
.collection("citee")
.doc(citee)
.set({"Description": list}).then((value) => print(" Deleted"));
}
}
A helpful way to consider Firestore "Arrays" is that they are ABSOLUTELY NOT ARRAYS - they are ORDERED LISTS (ordered either by the order they were added to the array, or the order they were in in an array passed to the API as an array), and the "number" shown is the order, not an index. The only way to "identify" a single element in a Firestore Array[ordered list] is by it's exact and complete value. It is VERY unfortunate they chose the name "array".
That said, when you read a document, the result presented to your CODE is in the form of an array, and GAINS the ability to refer to an element by index - which is why you have to EITHER:
=> at the backend / API call, specify an element by "value", which in this case is the ENTIRE object on the list
OR
=> at the Client, read the document, delete the desired element either by index or value, then write the entire array back to the backend.
I'm trying to allow a user to mark an item being built by a ListViewBuilder as a favorite. With my current code, when a user favorites one episode, all episodes are marked as favorite. I would like the user to be able to add each episode individually as a favorite and persist that favorite after a restart. I have the data saved to a firebase database but it seems like this should be handled in the app itself.
What is the best way to do this? Thanks!
Here is my current code:
class Epi {
final String endTime;
final String name;
final String networkName;
final String showName;
final String startTime;
Epi({this.endTime, this.name, this.networkName, this.showName, this.startTime});
factory Epi.fromJson(Map<dynamic, dynamic> parsedJson) {
DateTime endTimeCon = DateTime.parse(parsedJson['endTime']);
String newEndTime = formatDate(endTimeCon, [yyyy, '/', mm, '/', dd, ' ', hh, ':', nn, ':', ss, ' ', am]);
DateTime startTimeCon = DateTime.parse(parsedJson['startTime']);
String newStartTime = formatDate(startTimeCon, [yyyy, '/', mm, '/', dd, ' ', hh, ':', nn, ':', ss, ' ', am]);
return Epi(
endTime: newEndTime,
name: parsedJson['name'],
networkName: parsedJson['networkName'],
showName: parsedJson['showName'],
startTime: newStartTime,
);
}
}
bool _isFavorited = true;
void _toggleFavorite() {
setState(() {
if (_isFavorited) {
_isFavorited = false;
} else {
_isFavorited = true;
}
});
}
body: Column(
children: <Widget>[
SizedBox(height: 5.0),
Expanded(
child: ListView.builder(
itemCount: elist.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
selectEpisode(index);
},
child: Card(
child: Column(
children: <Widget>[
ListTile(
title: Text(elist[index].name),
subtitle: Text(elist[index].startTime),
leading: IconButton(
icon: (_isFavorited ? Icon(Icons.favorite_border) : Icon(Icons.favorite)),
color: Colors.red[500],
onPressed: _toggleFavorite,
),
trailing: Icon(Icons.arrow_forward_ios)
)
],
),
),
);
}),
),
],
)
In my Congress Fahrplan App (Github) I'm doing exactly what you want to achieve.
In favorite_provider I store the value in the object itself and add it to my list of favorited objects. Whenever an object is added to this list, the list is written to the disk as JSON with my file_storage class.
When the app is restarted, the objects are fetched from a REST API. Then I match their IDs with the objects from the local JSON and set whether they are favorited or not to restore the favorite state.
Making a favorite list of items basically differs based on the app design and you might as well develop your own logic for this purpose. Now, while what #benjaminschilling33 posted is true, you can also achieve this in a simple way.
What I would do is, add a boolean called isFavorite on the constructor like this:
class Epi {
final String endTime;
final String name;
final String networkName;
final String showName;
final String startTime;
bool isFavorite;
}
//initialize the isFavorite to false cause no item in your list is is favorite at the beginning
Epi({this.endTime, this.name, this.networkName, this.showName, this.startTime, this.isFavorite=false});
//lets create a list _episode which contains all the movie for demonstration purpose
List<Epi> _episode = [Epi(initialized data)];
//create a getter for the list
List<Epi> get episode{
return _episode.where((Epi episode) => episod.isFavorite).toList(); //this will return a list where the isFavorite is true
}
//You can then set your icon (in the list-tile) based on your isFavorite result
ListTile(
...
icon: Icons(elist[index].isFavorite?Icon(Icons.favorite):Icon(Icons.favorite_border);
)
//Then adjust the logic on your onPress
onPressed: (){
setState((){
elist[index].isFavorite=!elist[index].isFavorite //this will vary from true to false and vice versa when pressed
});
}
This is the simplest way to add list of items that is favorited by the user rather than building another list for the favorite section. What I wrote here is offline based test you can achieve and the key take away is the where property which is:
List<Epi> episode=[some data]
epsode.where((Epi episode)=>episode.isFavorite).toList();
You can use this method even after deploying your app to the cloud database by creating that attribute in your database based on the user's id.
I need to implement lists of buttons in a column depending on the data entry. So, for that I have to use for loop. Each button requires two entires id, text. I can make it with List. But it accepts only string value not the integer.
This is the code I tried.
code
Widget getTextWidgets(List<String> strings)
{
List<Widget> list = new List<Widget>();
for(var i = 0; i < strings.length; i++){
list.add(new ButtonForHome(
lable: strings[i],
onPressed: (){},
));
}
return new Column(children: list);
}
I want to put id in onPressed event. How can I implement in the Flutter?
You should use Listview instead of Column
SAMPLE CODE
getTextWidgets() {
return ListView.builder(
itemCount: yourList.length,
itemBuilder: (context, itemIndex) {
return MaterialButton(
child: Text(yourList[itemIndex]),
onPressed: () {
debugPrint('Clicked index $itemIndex');
});
});
}
Now your question
I want to put id in onPressed event. How can I implement in the Flutter?
You can create a POJO class like this
class DataModel{
String name;
int id;
DataModel(this.name, this.id);
}
Now create list of your POJO class
List<DataModel> list= List<DataModel>();
Now add data in your list like this
list.add(DataModel("name", 1));
list.add(DataModel("name", 2));
list.add(DataModel("name", 3));
Now you can use it like this way
getTextWidgets() {
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, itemIndex) {
return MaterialButton(
child: Text(list[itemIndex].name),
onPressed: () {
debugPrint('Clicked item name '+list[itemIndex].name);
debugPrint('Clicked item ID '+list[itemIndex].id.toString());
});
});
}
Nilesh Rathod has indeed given the descriptive answer for the same. In flutter there is also, a way to achieve this, which is quite similar to POJO class, is
To create own widget and specify the fields needs to be passed when we are using the widget
Add the widget to the list, with the data specified for passing
You can track, the id, by pressing itself also
I can clearly see that, you have created your own widget named as ButtonForHome, which takes in label for now. What you can do is, to make your widget takes in two argument, and you can do it like this:
class ButtonForHome extends StatelessWidget {
final String label;
final int id; // this will save your day
// #required will not let user to skip the specified key to be left null
ButtonForHome({#required this.label, #required this.id});
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: RaisedButton(
color: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
),
child: Text(this.label),
onPressed: () => print(this.id) // Here you print your id using your button only
)
);
}
}
Now creating your button with a list, or adding via list
You can do it via ListView.builder()
You can do it via your way, i.e., List<Widget>.add()
I am gonna show the solution in your way only:
Widget getTextWidgets(List<String> strings){
List<Widget> list = new List<Widget>();
for(var i = 0; i < strings.length; i++){
list.add(ButtonForHome(
id: i, // passing the i value only, for clear int values
label: strings[i]
));
}
return Column(children: list);
}
With the new flutter in place, you don't need to do new every time while defining a widget. It understands now, so no need of const, new at all
So, wherever you populate your getTextWidget, it will show up the Widgte ButtonForHome, which has unique id, and label. Now the ButtonForHome, prints the id of that particular widget which was passed uniquely. So now, you can see your result happening.
I hope this is what you were looking for. Try it, and let me know.