Flutter - SharedPreferences saving list - firebase

I have an app that is connected to Firebase, and I am making a screen that will show all notifications sent, for that I am using SharedPreferences. But as the notification arrives on a map, I am placing it inside a List <Map <dynamic, dynamic >>, to show the notification.
String title, body;
Map<dynamic, dynamic> notific;
List<Map<dynamic, dynamic>> notifList = [];
///Widget
return Scaffold(
extendBody: true,
backgroundColor: widget._colors.white,
appBar: appBar,
body: ListView.builder(
itemCount: notifList.length,
itemBuilder: (context, i) {
return Card(
margin: EdgeInsets.all(10),
elevation: 4,
child: ListTile(
title: Text(
notifList.elementAt(i)['title'],
),
subtitle: Text(
notifList.elementAt(i)['body'],
),
),
);
},
),
);
}
Firebase Method
Future<dynamic> fcmMessageReceiver() async {
FirebaseMessaging.instance.getInitialMessage().then((value) {
if (value != null) {}
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
if (message.notification != null) {
notific = {
'title': message.notification.title,
'body': message.notification.body
};
notifList.add(notific);
setState(() {
title = message.notification.title;
body = message.notification.body;
});
print('MENSAGEM: $notific');
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {});
}
Shared Preferences method, being called on initState()
void savePush() async {
SharedPreferences sharePref = await SharedPreferences.getInstance();
final strList = sharePref.getStringList('push')??[];
sharePref.setStringList('push', notifList.toString());
}
My question is, how can I keep these notifications, so whenever I want to see them, I can get them easily, and set up the Cards with the notifications?

So, there are many approaches to solve this issue, some of my approaches will be to convert each message to a JSON encoded string and then push it to the sharedPreference.setStringList(list). Another way is to make the whole list a JSON encoded string and save it to SharedPreferences like a string by calling sharedPreference.setString(list).
Let's say your List of the message is like this:
List<Map<String, dynamic>> messagesForUI = [];
And, you've initialized SharedPreferences and previous messages from SharedPreferences like this:
SharedPreferences sharedPreference = await SharedPreferences.getInstance();
List<String> sharedPreferenceMessages = [];
Now, to retrieve all your previous messages from SharedPreferences and then set the previous messages to messagesForUI inside the initState method, you can do this:
sharedPreferenceMessages = sharedPreference.getStringList("messages") ?? [];
sharedPreferenceMessages.forEach((element) {
Map<String, dynamic> messageMap = Map<String, dynamic>.from(json.decode(element));
messagesForUI.add(messageMap);
});
Now, you've your list ready to roll.
Let's say you have a new message from FCM and you want to save it to the SharedPreferences. Let's save the new message this way:
Map<String, dynamic> newMessage = Map<String, dynamic>.from(fcmMessage);
setState((){
messagesForUI.add(newMessage);
});
String newMessageJson = json.encode(newMessage);
sharedPreferenceMessages.add(newMessageJson);
sharedPreference.setStringList("messages", sharedPreferenceMessages);
There you go. You can also save messages to SharedPreferences via calling sharedPreference.setString(map), just like this approach. If need a demonstration of that process, just comment here.
Sample code:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class NotificationRoute extends StatefulWidget {
#override
_NotificationRouteState createState() => _NotificationRouteState();
}
class _NotificationRouteState extends State<NotificationRoute> {
List<Map<String, dynamic>> messagesForUI = [];
List<String> sharedPreferenceMessages = [];
SharedPreferences sharedPreference;
#override
void initState() {
init();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: messagesForUI.isEmpty
? Center(
child: Text("No notifications"),
)
: ListView.builder(
itemBuilder: (context, index) {
final Map<String, dynamic> message = messagesForUI[index];
return ListTile(
title: Text(message["title"]),
subtitle: Text(message["body"]),
);
},
shrinkWrap: true,
physics: ScrollPhysics(),
scrollDirection: Axis.vertical,
itemCount: messagesForUI.length,
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Map<String, dynamic> newMessage = {"title": "test title", "body": "test body"};
setState(() {
messagesForUI.add(newMessage);
});
String newMessageJson = json.encode(newMessage);
sharedPreferenceMessages.add(newMessageJson);
sharedPreference.setStringList("messages", sharedPreferenceMessages);
},
child: Icon(Icons.add),
),
);
}
init() async {
sharedPreference = await SharedPreferences.getInstance();
sharedPreferenceMessages = sharedPreference.getStringList("messages") ?? [];
sharedPreferenceMessages.forEach((element) {
Map<String, dynamic> messageMap = Map<String, dynamic>.from(json.decode(element));
messagesForUI.add(messageMap);
});
}
}
Now, as I don't have any FCM set-up on my project, I just replicate the message add process to the SharedPreference via FloatingActionButton.
Happy coding :D

Related

Get all documents from a collection from Firestore - flutter

I am trying to get all the documents with their fields from firestore collection, but it's not working. I did this:
final _fireStore = FirebaseFirestore.instance;
Future<void> getData() async{
QuerySnapshot querySnapshot = await _fireStore.collection('addsaidas').get();;
final allData = querySnapshot.docs.map((doc) => doc.data()).toList();
print(allData);
}
But the screen still is empty. The code is running, but nothing appears.
Your code seems fine to me at first glance. For issue regarding this line
But the screen still is empty. The code is running, but nothing appears.
To display the result on screen we have to use Widgets provided by flutter.
Here’s one Example with using ListView and also printing the result in the console onPress of the FloatingActionButton :
class MyHomePage extends StatelessWidget {
MyHomePage({Key? key}) : super(key: key);
final _fireStore = FirebaseFirestore.instance;
final ref =
FirebaseFirestore.instance.collection('addsaidas').snapshots();
Future<void> getData() async {
QuerySnapshot querySnapshot =
await _fireStore.collection('addsaidas').get();
final allData = querySnapshot.docs.map((doc) => doc.data()).toList();
for (var dataMap in allData) {
if (dataMap is Map) {
// add a type check to ensure dataMap is a Map
for (var key in dataMap.keys) {
print('$key: ${dataMap[key]}'); //printing document fields using keys
}
print('----------------------');
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('My Screen')),
body: StreamBuilder<QuerySnapshot>(
stream: ref,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final documents = snapshot.data!.docs;
return ListView.builder(
itemCount: documents.length,
itemBuilder: (context, index) {
final document = documents[index];
final data = document.data() as Map<String, dynamic>;
return ListTile(
title: Text(data['nomesaida']),
subtitle: Text(data['datasaida']),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: getData,
backgroundColor: Colors.green,
child: const Icon(Icons.navigation),
),
);
}
}
You can print other fields as above mentioned.

Pass fetched value to a firestore reference to flutter's streambuilder

I'm accessing a user's favorite group which is inside groupfav in Firestore, when I get it I want to give it as part of the reference to the streambuilder stream:, so that it knows what to show in a list, but I can't pass the variable that contains the favorite group, what should I do or what am I doing wrong?
static String? userID = FirebaseAuth.instance.currentUser?.uid; // get current user id
static var taskColeccion = FirebaseFirestore.instance.collection("usuarios");
var tack = taskColeccion.doc("$userID").get().then((value) {
var groupfav = value.data()!["groupfav"]; // value i get from firestore
return groupfav;
});
late Stream<QuerySnapshot> task = FirebaseFirestore.instance
.collection("groups")
.doc(groupfav) // pass the obtained value
.collection("tareas")
.snapshots();
photo of firestore
The photo shows how Firestore's logic is and the value marked in green is what I must pass to the late Stream<QuerySnapshot> task... in its reference, logically it is a random value that I would not know. thanks for any help!
this is what the code looks like now (I took things that were not important)
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
static String? userID = FirebaseAuth.instance.currentUser?.uid;
static final taskColeccion =
FirebaseFirestore.instance.collection("usuarios");
String groupfav = '';
final tack = taskColeccion.doc("$userID").get().then((value) {
groupfav = value.data()!["groupfav"];
return groupfav;
});
Stream<QuerySnapshot> task = FirebaseFirestore.instance
.collection("groups")
.doc(groupfav) // pass the obtained value
.collection("tareas")
.snapshots();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Home"),
automaticallyImplyLeading: false,
),
body: StreamBuilder(
stream: task,
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
if (snapshot.hasError) {
return const Text("error");
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("cargando");
}
final data = snapshot.requireData;
return ListView.builder(
itemCount: data.size,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text("${data.docs[index]['titulo']}"),
subtitle: Text("${data.docs[index]['contenido']}"),
onTap: () {},
trailing: IconButton(
icon: const Icon(Icons.delete),
color: Colors.red[200],
onPressed: () {
// delete function
},
),
),
);
},
);
},
),
);
}
}
You just need to declare groupfav outside of the scope of the get method of taskColeccion;
The way you have it, the variable no longer exists by the time you're trying to pass it into the task stream.
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
static String? userID = FirebaseAuth.instance.currentUser?.uid;
static final taskColeccion =
FirebaseFirestore.instance.collection("usuarios");
String groupfav = '';
late Stream<QuerySnapshot> task;
#override
void initState() {
super.initState();
taskColeccion.doc("$userID").get().then((value) {
groupfav = value.data()!["groupfav"];
return groupfav;
});
task = FirebaseFirestore.instance
.collection("groups")
.doc(groupfav) // pass the obtained value
.collection("tareas")
.snapshots();
}

How to get the related image from Firebase storage in flutter

I'm trying to build a list of widgets that are displayed using streambuilder for each entry in my cloud firestore. Here's the code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ProperHomeScreen extends StatefulWidget {
#override
_ProperHomeScreenState createState() => _ProperHomeScreenState();
}
class _ProperHomeScreenState extends State<ProperHomeScreen> {
final _firestore = Firestore.instance;
String _downloadURL;
StorageReference _reference = FirebaseStorage.instance.ref();
#override
void initState() {
super.initState();
}
void postsStream() async {
await for (var snapshot in _firestore.collection('posts').snapshots()) {
for (var post in snapshot.documents) {
print(post.data);
}
}
}
testFunction(postImage) async {
print('Here\'s the postImage data from test function: $postImage');
String downloadAddress = await _reference.child(postImage).getDownloadURL();
setState(() {
_downloadURL = downloadAddress;
});
print('THIS IS THE DOWNLOAD URL FROM THE TEST FUNCTION! ::: $_downloadURL');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
StreamBuilder<QuerySnapshot> (
stream: _firestore.collection('posts').snapshots(),
builder: (context, snapshot) {
if(!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.lightBlueAccent,
),
);
}
final posts = snapshot.data.documents;
List<Widget> postWidgets = [];
for (var post in posts) {
final postText = post.data['questionOne'];
final postSender = post.data['email'];
final postImage = post.data['filePath'];
testFunction(postImage);
print('THIS IS THE DOWNLOAD ADDRESS : $_downloadURL');
final postWidget = Container(
child: Column(
children: <Widget>[
Text('$postText from $postSender with image : $postImage'),
Image.network('$_downloadURL'),
],
),
);
postWidgets.add(postWidget);
}
return Column(
children: postWidgets,
);
},
),
],
),
);
}
}
In the console, it is printing urls fine, but the problem I have is that it keeps running the testFunction() continuously until I stop main.dart.
I'm trying to show a different image for each post.
Essentially, I am saving data in cloud firestore and saving images in firebase storage. I'm storing the file name of the image in cloud firestore so that I can access it.
Here's a sample of how I'm saving a post in firestore:
void submitPostSection() {
DateTime now = DateTime.now();
_firestore.collection('posts').add({
'email': loggedInUser.email,
'date': now,
'questionOne': widget.questionOne, //this is a simple string. Example data: 'Here is the latest post today 31st July 2020'
'filePath' : _filePath, // this is just the image name that its saved as in firebase storage. datatype for this is string. here's an example of the data: 'myimage2.jpg'
});
}
I think the problem is because the method keeps getting called and setting state of _downloadURL. I'm not really sure the best way to go about this.
Any ideas?
Thanks in advance!
The problem is that inside testFunction() you are calling setState() which will keep calling the build() method, you can do the following:
List<String> listOfUrl = [];
for (var post in posts) {
final postText = post.data['questionOne'];
final postSender = post.data['email'];
final postImage = post.data['filePath'];
String downloadAddress = await _reference.child(postImage).getDownloadURL();
listOfUrl.add(downloadAddress);
}
ListView.builder(
shrinkWrap: true,
itemCount: listOfUrl.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.network(listOfUrl[index]),
],
),
);
});
add the urls inside a list and then use a listview to display them.
I've solved my problem. I deleted the testFunction() and just saved the actual imageURL inside the cloud firestore document. Then I can access it really easily by adding the following line:
final postImageUrl = post.data['imageURL'];

How to retrieve a Firebase Storage image stream in flutter?

I've got a few photo's I've uploaded into my firebase storage under a file called 'photos' and I want to be able to retrieve them onto my app through a stream. I have done this before through Firebase cloud database by tapping into the Firestore.instance.collection('messages').snapshots() property in my StreamBuilder, but I don't know how to access the firebase storage snapshots and upload them as a stream into my app.
This was my code for the messages snapshot, I hope it helps:
final _firestore = Firestore.instance;
void messagesStream() async {
await for (var message in _firestore.collection('messages').snapshots()){
for (var snapshot in message.documents){
print(snapshot.data);
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('messages').snapshots(),
builder: (context, snapshot){
if (!snapshot.hasData){
return Center(
child: CircularProgressIndicator(backgroundColor: Colors.lightBlueAccent,),
);
} else {
final messages = snapshot.data.documents;
List<Text> messageWidgets = [];
for (var message in messages){
final messageText = message.data['text'];
final messageSender = message.data['sender'];
final messageWidget = Text('$messageText from $messageSender');
messageWidgets.add(messageWidget);
}
return Column(children: messageWidgets,);
}
}
),
),
},
So I figured out you can't create a stream from the firebase storage, but what I could do was, in my firebase cloud database, start a new collection called 'my_collection' and in a new document, create an auto-ID, with a field called 'image' which is a string, with an http reference to an image that is on the internet, or one you can upload to the internet (this is what I did on imgur.com, credit to them)! Here is my code below, I hope it helps others! If it doesn't, have a look at this code written by iampawan, he helped me a tonne!
https://github.com/iampawan/FlutterWithFirebase
class MyList extends StatefulWidget {
#override
_MyListState createState() => _MyListState();
}
class _MyListState extends State<MyList> {
StreamSubscription<QuerySnapshot> subscription;
List <DocumentSnapshot> myList;
final CollectionReference collectionReference = Firestore.instance.collection('my_collection');
final DocumentReference documentReference = Firestore.instance.collection('my_collection').document('GFWRerw45DW5GB54p');
#override
void initState() {
super.initState();
subscription = collectionReference.snapshots().listen((datasnapshot) {
setState(() {
myList = datasnapshot.documents;
});
});
}
#override
void dispose() {
subscription?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return myList != null ?
ListView.builder(
itemCount: myList.length,
itemBuilder: (context, index){
String imgPath = myList[index].data['image'];
return MyCard(assetImage: Image.network(imgPath), function:
(){
if (imgPath == myList[0].data['image']){
Navigator.pushNamed(context, MyMenu.id);
} else if (imgPath == myList[1].data['image']){
Navigator.pushNamed(context, YourMenu.id);
} else if (imgPath == myList[2].data['image']){
Navigator.pushNamed(context, HisMenu.id);
} else if (imgPath == myList[3].data['image']){
Navigator.pushNamed(context, HerMenu.id);
}
},);
})
: Center(child: CircularProgressIndicator(),
);
}
}
Just to note, MyCard is it's own page with it's own constructor that requires an assetImage and a function for the user to be pushed to a new screen:
MyCard({#required this.assetImage, #required this.function});
final Image assetImage;
final Function function;

How do I use an async method to build a List Object?

I am getting an error that says that the method .length is calling on a null object _genreList.
I am using an async method to get data from a local asset sqlite database to which is a list of genre's. Which then I use ListView.builder in order to display that list on the screen. This is the code to obtain the data...
Future getGenreData() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "asset_sample_sqlite.db");
ByteData data = await rootBundle.load(join("assets", "sample_sqlite.db"));
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await new File(path).writeAsBytes(bytes);
Database db = await openDatabase(path);
_genreList = await db.rawQuery('SELECT genre_name[] FROM tbl_genres');
print(_genreList);
await db.close();
}
How do I use this method inside the build Widget method so that I can access the _genreList when I use ListView.builder? like so..
#override
Widget build(BuildContext context) {
return Scaffold(
body: new ListView.builder(
itemCount: _genreList.length, //need to access the genreList here
itemBuilder: (BuildContext context, int index) {
return new Card(
child: new ListTile(
title: new Text("${_genreList[index]}"),
onTap: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => BookPage(id: index),
),
);
}
),
);
}
),
);
}
The end goal here is to display a list of genres (from the tbl_genres in my sqlite database) that will be able to pass through data to the next page to show a list of books (from the tbl_books in my sqlite database) related to that genre.
The whole point of programming asynchronously is that your user interface can stay alive while you are doing time consuming work in the background. So you need (and want) to display something like a CircularProgressIndicator or even a blank page (e.g. a Container), while the application is loading.
There are at least these two ways of doing that:
Make the widget stateful and introduce a state field loading, that you initialize to true and set to false when your data (in another field) is ready. Your code would look like that:
import 'package:flutter/material.dart';
class GenresPage extends StatefulWidget {
#override
_GenresPageState createState() => _GenresPageState();
}
class _GenresPageState extends State<GenresPage> {
bool loading;
List<String> genreNames;
#override
void initState() {
super.initState();
loading = true;
getGenreData();
}
Future getGenreData() async {
final genreData = await actuallyGetThoseNames();
setState(() {
genreNames = genreData;
loading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: !loading ? new ListView.builder(
itemCount: genreNames.length,
itemBuilder: (context, index) {
return new Card(
child: new ListTile(
title: new Text("${genreNames[index]}"),
),
);
},
) : CircularProgressIndicator(), // or Container()
);
}
}
Use a FutureBuilder. Therefore you would need to refactor your getGenreData method to return the list as a Future<List<String>>.

Resources