Flutter upload list to Google Firestore - list

I would like to add a list from my flutter test app to my Google Firestore.
This is my method, which adds all the data:
void postToFireStore(
{String mediaUrl, String location, String description}) {
var reference = Firestore.instance.collection('insta_posts');
reference.add({
"marked_friends": [chipstuete.toString()],
"username": currentUserModel.username,
"location": location,
"likes": {},
"mediaUrl": mediaUrl,
"description": description,
"ownerId": googleSignIn.currentUser.id,
"timestamp": new DateTime.now().toString(),
}).then((DocumentReference doc) {
String docId = doc.documentID;
reference.document(docId).updateData({"postId": docId});
});
}
Everything is working fine, expect the list "marked_friends"...
The list "chipstuete" has multiple strings:
[Martin Seubert, Lena Hessler, Vivien Jones]
But my Firestore looks like that:
At the moment the whole list is stored in marked_friends[0]...
What do I need to change, that every entry of my list "chipstuete" is stored in a seperate field of my array "marked_friends" in Firestore?
Best regards!

You have to add a method in your AppProfile class that serializes it to a List.
So in your AppProfile class:
class AppProfile {
... // Whatever fields/methods you have
// Add this method
List<String> getFriendList() {
// Somehow implement it so it returns a List<String> based on your fields
return ['name1','name2','name3'];
}
}
Then you can do
"marked_friends": chipstuete.getFriendList(),

I have the solution.
Like SwiftingDuster said, I needed a new method which serializes it to a List:
List<String> toList() {
chipstuete.forEach((item) {
newtuete.add(item.toString());
});
return newtuete.toList();
}
After that I just call toList() in my postToFirestore() Method and add "marked_friends": newtuete. Thats it!

Related

Flutter - type 'List<dynamic>' is not a subtype of type 'List<Model>?'

I'm learning flutter by making an app following some youtube tutorials. I'm trying to make a listview of search results. I'm able to query and get data from node backend but there's this error while mapping the json to model.
The data I'm getting from api is like this:
{id: <uuid>,
userEmail: <email_string>,
profile: [{profileName: <profile_name_string>,
profileImage: <image_url_string>,
profileBio: <profile_bio_string>}]
}
With the new model class I made following an answer here I'm able to get profile model separately but when i try to get account model with all profiles I'm getting the error:type 'List<dynamic>' is not a subtype of type 'List<ProfileModel>?'. The model class is:
class AccountModel {
String userId;
String userEmail;
String? userPassword;
final List<ProfileModel>? profile;
AccountModel({
required this.userId,
required this.userEmail,
this.userPassword,
this.profile,
});
factory AccountModel.fromJson({required Map<String, dynamic> map}) {
return AccountModel(
userId: map['id'],
userEmail: map['userEmail'],
userPassword: map['userPassword'],
profile: map['profile']
.map((profileJson) => ProfileModel.fromJson(profileJson))
.toList(),
);
}
}
class ProfileModel {
String profileName;
String profileImage;
String? profileBio;
ProfileModel({
required this.profileName,
required this.profileImage,
this.profileBio,
});
factory ProfileModel.fromJson(profileJson, {Map<String, dynamic>? map}) {
if (map != null) {
return ProfileModel(
profileName: map['profileName'],
profileImage: map['profileImage'] ?? "default",
profileBio: map['profileBio'],
);
} else {
return ProfileModel(
profileName: profileJson['profileName'],
profileImage: profileJson['profileImage'] ?? "default",
profileBio: profileJson['profileBio'],
);
}
}
}
How to make the list work?
You can use List.from() in this case.
profile: map['profile'] != null
? List<ProfileModel>.from(
map['profile']?.map((p) => ProfileModel.fromJson(p)))
: null)
We are using fromMap here on ProfileModel, you can simplify just separation while both are same on ProfileModel.
More about List and List.from.
When you declared the list here as
final List<ProfileModel>? profile;
It expects the list to have only ProfileModels as ListItem even though with "?". The way to solve it is either declared a list without generic ProfileModel :
final List? profile;
Or to typecast the item you're pushing as ProfileModel.
2. profile: map['profile'] .map((profileJson) => ProfileModel.fromJson(profileJson) as ProfileModel) .toList(),
I don't know the output structures and such so try to experiment with typecasting if the above code doesn't work. May be typecasting after toList() method as List can work too.

Modelling Complex Types for DynamoDB in Kotlin

I have a DynamoDB table that I need to read/write to. I am trying to create a model for reading and writing from DynamoDB with Kotlin. But I keep encountering com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: MyModelDB[myMap]; could not unconvert attribute when I run dynamoDBMapper.scanPage(...). Some times myMap will be MyListOfMaps instead, but I guess it's from iterating the keys of a Map.
My code is below:
#DynamoDBTable(tableName = "") // Non-issue, I am assigning the table name in the DynamoDBMapper
data class MyModelDB(
#DynamoDBHashKey(attributeName = "id")
var id: String,
#DynamoDBAttribute(attributeName = "myMap")
var myMap: MyMap,
#DynamoDBAttribute(attributeName = "MyListOfMapItems")
var myListOfMapItems: List<MyMapItem>,
) {
constructor() : this(id = "", myMap = MyMap(), myListOfMaps = mutableListOf())
#DynamoDBDocument
class MyMap {
#get:DynamoDBAttribute(attributeName = "myMapAttr")
var myMapAttr: MyMapAttr = MyMapAttr()
#DynamoDBDocument
class MyMapAttr {
#get:DynamoDBAttribute(attributeName = "stringValue")
var stringValue: String = ""
}
}
#DynamoDBDocument
class MyMapItem {
#get:DynamoDBAttribute(attributeName = "myMapItemAttr")
var myMapItemAttr: String = ""
}
}
I am using the com.amazonaws:aws-java-sdk-dynamodb:1.11.500 package and my dynamoDBMapper is initialised with DynamoDBMapperConfig.Builder().build() (along with some other configurations).
My question is what am I doing wrong and why? I have also seen that some Java implementations use DynamoDBTypeConverter. Is it better and I should be using that instead?
Any examples would be appreciated!
A couple comments here. First, you are not using the AWS SDK for Kotlin. You are using another SDK and simply writing Kotlin code. Using this SDK, you are not getting full benefits of Kotlin such as support of Coroutines.
The AWS SDK for Kotlin (which does offer full support of Kotlin features) was just released as DEV Preview this week. See the DEV Guide:
Setting up the AWS SDK for Kotlin
However this SDK does not support this mapping as of now. To place items into an Amazon DynamoDB table using the AWS SDK for Kotlin, you need to use:
mutableMapOf<String, AttributeValue>
Full example here.
To map Java Objects to a DynamoDB table, you should look at using the DynamoDbEnhancedClient that is part of AWS SDK for Java V2. See this topic in the AWS SDK for Java V2 Developer Guide:
Mapping items in DynamoDB tables
You can find other example of using the Enhanced Client in the AWS Github repo.
Ok, I eventually got this working thanks to some help. I edited the question slightly after getting a better understanding. Here is how my data class eventually turned out. For Java users, Kotlin compiles to Java, so if you can figure out how the conversion works, the idea should be the same for your use too.
data class MyModelDB(
#DynamoDBHashKey(attributeName = "id")
var id: String = "",
#DynamoDBAttribute(attributeName = "myMap")
#DynamoDBTypeConverted(converter = MapConverter::class)
var myMap: Map<String, AttributeValue> = mutableMapOf(),
#DynamoDBAttribute(attributeName = "myList")
#DynamoDBTypeConverted(converter = ListConverter::class)
var myList: List<AttributeItem> = mutableListOf(),
) {
constructor() : this(id = "", myMap = MyMap(), myList = mutableListOf())
}
class MapConverter : DynamoDBTypeConverter<AttributeValue, Map<String,AttributeValue>> {
override fun convert(map: Map<String,AttributeValue>>): AttributeValue {
return AttributeValue().withM(map)
}
override fun unconvert(itemMap: AttributeValue?): Map<String,AttributeValue>>? {
return itemMap?.m
}
}
class ListConverter : DynamoDBTypeConverter<AttributeValue, List<AttributeValue>> {
override fun convert(list: List<AttributeValue>): AttributeValue {
return AttributeValue().withL(list)
}
override fun unconvert(itemList: AttributeValue?): List<AttributeValue>? {
return itemList?.l
}
}
This would at least let me use my custom converters to get my data out of DynamoDB. I would go on to define a separate data container class for use within my own application, and I created a method to serialize and unserialize between these 2 data objects. This is more of a preference for how you would like to handle the data, but this is it for me.
// For reading and writing to DynamoDB
class MyModelDB {
...
fun toMyModel(): MyModel {
...
}
}
// For use in my application
class MyModel {
var id: String = ""
var myMap: CustomObject = CustomObject()
var myList<CustomObject2> = mutableListOf()
fun toMyModelDB():MyModelDB {
...
}
}
Finally, we come to the implementation of the 2 toMyModel.*() methods. Let's start with input, this is what my columns looked like:
myMap:
{
"key1": {
"M": {
"subKey1": {
"S": "some"
},
"subKey2": {
"S": "string"
}
}
},
"key2": {
"M": {
"subKey1": {
"S": "other"
},
"subKey2": {
"S": "string"
}
}
}
}
myList:
[
{
"M": {
"key1": {
"S": "some"
},
"key2": {
"S": "string"
}
}
},
{
"M": {
"key1": {
"S": "some string"
},
"key3": {
"M": {
"key4": {
"S": "some string"
}
}
}
}
}
]
The trick then is to use com.amazonaws.services.dynamodbv2.model.AttributeValue to convert each field in the JSON. So if I wanted to access the value of subKey2 in key1 field of myMap, I would do something like this:
myModelDB.myMap["key1"]
?.m // Null check and get the value of key1, a map
?.get("subKey2") // Get the AttributeValue associated with the "subKey2" key
?.s // Get the value of "subKey2" as a String
The same applies to myList:
myModelDB.myList.foreach {
it?.m // Null check and get the map at the current index
?.get("key1") // Get the AttributeValue associated with the "key1"
...
}
Edit: Doubt this will be much of an issue, but I also updated my DynamoDB dependency to com.amazonaws:aws-java-sdk-dynamodb:1.12.126

Flutter - Best way to store a List of Objects every time I close the application?

The situation:
I'm very new to Flutter and mobile development, thus I don't know much about Dart; And I've read some solutions from people with similar problems but didn't manage to work these solutions to my own thing.
The problem:
I have a to-do app that has 2 Lists of Objects, and I want to store those Lists for whenever the user re-open the app.
I know its simple stuff but I feel like I'm storming towards this problem due to the lack of experience... And so I decided to come asking for some light.
What I've tried:
I have come across different solutions for this problem and all of them seemed way too complex to this case (compared to what I'm used to do when saving lists to the archive), including: encoding the list to a map and converting to a string before using SharedPreferences, using SQlite database (every tutorial I've come across made me feel like I'd be using a war tank to kill an ant, I'd say the same about firebase).
Structure of the problem:
ToDo screen with a ListView.builder calling 2 arrays: ongoing tasks and done tasks each of which I want to write to the phone whenever the user makes a change. IDK if I should only try to save those arrays from within the class from which they belong by calling some packages methods, or if I should try to store the entire application if such thing is possible.
Conclusion:
Is there a way to solve this in a simple way or I should use something robust like firebase just for that? even though I'm not used to work with firestore, and so I'm in the dark not knowing how to apply such thing to save data.
How my lists are structured:
List<Task> _tasks = [
Task(
name: "do something",
description: "try to do it fast!!!",
),
];
List<Task> _doneTasks = [
Task(
name: "task marked as done",
description: "something",
),
];
2022 Update with null safety
My original code example was more verbose than necessary. Using Darts factory constructor this can be done with way less code. This is also updated for null safety and using Hive instead of GetStorage.
First, add a toMap method which converts the object to a Map, then a fromMap constructor which returns a Task object from a Map that was saved in storage.
class Task {
final String name;
final String description;
Task({required this.name, required this.description});
Map<String, dynamic> toMap() {
return {'name': this.name, 'description': this.description};
}
factory Task.fromMap(Map map) {
return Task(
name: map['name'],
description: map['description'],
);
}
String toString() {
return 'name: $name description: $description';
}
}
Updated Demo Page
class StorageDemo extends StatefulWidget {
#override
_StorageDemoState createState() => _StorageDemoState();
}
class _StorageDemoState extends State<StorageDemo> {
List<Task> _tasks = [];
final box = Hive.box('taskBox');
// separate list for storing maps/ restoreTask function
//populates _tasks from this list on initState
List storageList = [];
void addAndStoreTask(Task task) {
_tasks.add(task);
storageList.add(task.toMap()); // adding temp map to storageList
box.put('tasks', storageList); // adding list of maps to storage
}
void restoreTasks() {
storageList = box.get('tasks') ?? []; // initializing list from storage
// looping through the storage list to parse out Task objects from maps
for (final map in storageList) {
_tasks
.add(Task.fromMap(map)); // adding Tasks back to your normal Task list
}
}
// looping through your list to see whats inside
void printTasks() {
for (final task in _tasks) {
log(task.toString());
}
}
void clearTasks() {
_tasks.clear();
storageList.clear();
box.clear();
}
#override
void initState() {
super.initState();
restoreTasks(); // restore list from storing in initState
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Container(),
),
TextButton(
onPressed: () {
final task =
Task(description: 'test description', name: 'test name');
addAndStoreTask(task);
},
child: Text('Add Task'),
),
TextButton(
onPressed: () {
printTasks();
},
child: Text('Print Storage'),
),
TextButton(
onPressed: () {
clearTasks();
},
child: Text('Clear Tasks'),
),
],
),
);
}
}
Updated Storage Init
void main() async {
await Hive.initFlutter();
await Hive.openBox('taskBox');
runApp(MyApp());
}
Original Answer
So generally speaking, once you want to store anything other than a primitive type ie. String int etc... things get a bit more complex because they have to converted to something that's readable by any storage solution.
So despite Tasks being a basic object with a couple strings, SharedPreferences or anything else doesn't know what a Task is or what to do with it.
I suggest in general reading about json serialization, as you'll need to know about it either way. This is a good place to start and here is another good article about it.
All that being said, it can also be done without json by converting your task to a Map (which is what json serialization does anyway) and storing it to a list of maps. I'll show you an example of doing this manually without json. But again, its in your best interest to buckle down and spend some time learning it.
This example will use Get Storage, which is like SharedPreferences but easier because you don't need separate methods for different data types, just read and write.
I don't know how you're adding tasks in your app, but this is just a basic example of storing a list of Task objects. Any solution that doesn't involve online storage requires storing locally, and retrieving from storage on app start.
So let's say here is your Task object.
class Task {
final String name;
final String description;
Task({this.name, this.description});
}
Put this in your main method before running your app
await GetStorage.init();
You'll need to add async to your main, so if you're not familiar with how that works it looks like this.
void main() async {
await GetStorage.init();
runApp(MyApp());
}
Normally I would NEVER do all this logic inside a stateful widget, but instead implement a state management solution and do it in a class outside of the UI, but that's a whole different discussion. I also recommend checking out GetX, Riverpod, or Provider reading about them and seeing which one strikes you as the easiest to learn. GetX gets my vote for simplicity and functionality.
But since you're just starting out I'll omit that part of it and just put all these functions in the UI page for now.
Also instead of only storing when app closes, which can also be done, its easier to just store anytime there is a change to the list.
Here's a page with some buttons to add, clear, and print storage so you can see exactly whats in your list after app restart.
If you understand whats going on here you should be able to do this in your app, or study up on json and do it that way. Either way, you need to wrap your head around Maps and how local storage works with any of the available solutions.
class StorageDemo extends StatefulWidget {
#override
_StorageDemoState createState() => _StorageDemoState();
}
class _StorageDemoState extends State<StorageDemo> {
List<Task> _tasks = [];
final box = GetStorage(); // list of maps gets stored here
// separate list for storing maps/ restoreTask function
//populates _tasks from this list on initState
List storageList = [];
void addAndStoreTask(Task task) {
_tasks.add(task);
final storageMap = {}; // temporary map that gets added to storage
final index = _tasks.length; // for unique map keys
final nameKey = 'name$index';
final descriptionKey = 'description$index';
// adding task properties to temporary map
storageMap[nameKey] = task.name;
storageMap[descriptionKey] = task.description;
storageList.add(storageMap); // adding temp map to storageList
box.write('tasks', storageList); // adding list of maps to storage
}
void restoreTasks() {
storageList = box.read('tasks'); // initializing list from storage
String nameKey, descriptionKey;
// looping through the storage list to parse out Task objects from maps
for (int i = 0; i < storageList.length; i++) {
final map = storageList[i];
// index for retreival keys accounting for index starting at 0
final index = i + 1;
nameKey = 'name$index';
descriptionKey = 'description$index';
// recreating Task objects from storage
final task = Task(name: map[nameKey], description: map[descriptionKey]);
_tasks.add(task); // adding Tasks back to your normal Task list
}
}
// looping through you list to see whats inside
void printTasks() {
for (int i = 0; i < _tasks.length; i++) {
debugPrint(
'Task ${i + 1} name ${_tasks[i].name} description: ${_tasks[i].description}');
}
}
void clearTasks() {
_tasks.clear();
storageList.clear();
box.erase();
}
#override
void initState() {
super.initState();
restoreTasks(); // restore list from storing in initState
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Container(),
),
TextButton(
onPressed: () {
final task =
Task(description: 'test description', name: 'test name');
addAndStoreTask(task);
},
child: Text('Add Task'),
),
TextButton(
onPressed: () {
printTasks();
},
child: Text('Print Storage'),
),
TextButton(
onPressed: () {
clearTasks();
},
child: Text('Clear Tasks'),
),
],
),
);
}
}
Welcome to Flutter and Dart! Things might seem daunting in the start, but rewarding later on. Think of it logically, how is data to be stored outside the state of the app?
It has to be fetched from some data storage, this can either from the devices storage, or from a remote database.
For the device storage, you have the two options you mentioned, but SQlite would be an overkill for this task, however sharedPreferences isn't daunting as it seems, and very easy to use once you get the hang of it.
It's basically storing data on the device in the form of a string, along with a uniquekey to retrieve that data later on. This key is also a string.
For your app, you only need two keys: 'tasks' & 'completedTasks'.
Where are you facing trouble? encoding the data as a string? Or converting it to a map first then encoding it?
You're other option would be a remote database, that you send data over the internet, Firebase is only one of the possible solutions, besides building your own server, API and database, which is definitely an overkill for this situation as well.
However, since you are still getting the hang of things, this would be a good project to start with shared preferences, then in phase two, look into firebase, so your ToDo list items can be synced across multiple devices. This also comes with the added benefit you will gain from learning firebase, I strongly advise you look into it.
Every new language is daunting in the start, but expecting things to be 1+1=2 form the start will not get you where you want to be, and I'm certain you did not start learning Flutter to only make todo apps, but what you learn from this app will prepare you for what the future holds.
My advise, get comfortable with being uncomfortable, and remember why you started this journey, and whatever help you need, the community will never disappoint, just meet us or meet yourself half way.

How to append data inside loop in list in Flutter dart?

I'm new to flutter dart language. I'm trying to insert a search widget for that I need to create a list containing all JSON values parsed from an external API call. I was able to fetch data from API but I couldn't figure out a way to append this dynamic data inside a constructor class that I created to parse individual values.
output data from API which I receive looks like this
[{name: nikhil, uploadID: dfkjakds1213}, {name: nikhil, uploadID:
dfkjakds1213}, {name: nikhil, uploadID: dfkjakds1213}, {name: nikhil,
uploadID: dfkjakds1213}, {name: nkks, uploadID: szjxb771}, {name:
nkks, uploadID: szjxb771}...]
now i want to add this 2-d list into myclass list, i can't figure out how to do that? myclass list looks like this, with static data
List<myclass> words = [
myclass("nikhil", "adnfk"),
myclass("john", "asdfadsf"),
myclass("smith", "adf"),
myclass("stuart", "asdfdf"),
];
i want to make make it dynamic using some loops or something, like this
class myclass {
String name;
String uploadid;
myclass(this.name, this.uploadid);
}
getvalue() {
AuthService().getallStaff("staff", "all").then((val) {
List<myclass> words = [
for (var item in val.data)
{
myclass(item['name'], item['uploadid']),
}
];
});
}
but I get this error
The element type 'Set<myclass>' can't be assigned to the list type 'myclass'
How can I fix this?
The for loop inside an array is not allowed to use {}. It will turn items => one set of items.
The result of your case will become A set of myclass in an array:
List<myclass> words = [
{
myclass("nikhil", "adnfk"),
myclass("john", "asdfadsf"),
...
}
]
You can just remove the { }:
List<myclass> words = [
for (var item in val.data)
myclass(item['name'], item['uploadid']),
];
or use map (1-to-1):
List<myclass> words = val.data.map((item)=>myclass(item['name'],item['uploadid'])).toList();

how to sort embedded records in ember without using ArrayController

I want to sort embedded records in my content. Initially I had separated the embedded record as an ArrayController and did sorting on it - it was pretty straight forward, but now I am told that I should use just embedded records without ArrayController. I followed http://www.javascriptkit.com/javatutors/arraysort2.shtml to sort the array objects and the content is getting sorted but the view is not getting updated accordingly. My function looks like :
setSort: function (sort) {
var sortedContent = this.get('content.analyticsRunParameters');
sortedContent.sort(function(a, b){
var colA=a.get(sort).toLowerCase(), colB=b.get(sort).toLowerCase();
if (colA < colB) //sort string ascending
return -1;
if (colA > colB)
return 1;
return 0; //default return value (no sorting)
});
this.set('content.analyticsRunParameters',sortedContent);
console.log(sortedContent);//is sorted
console.log(this.get('content.analyticsRunParameters'));//is sorted
}
Is there a way to update the view when my content is sorted? Or using ArrayController the only way around? Thanks.
I found the solution in another post here : Ember.ArrayProxy changes not triggering handlebars #each update
I don't know if this is a best solution either. It seems like I should be just able to sort the array objects without the help of array proxy. I had to make minor modification in the transform I was using to return ArrayProxy instead of normal array of objects like :
AS.AnalyticsRunParameterTransform = DS.Transform.extend({
//return array of ember objects
deserialize: function (serialized) {
var objects = [];
for (var key in serialized) {
objects.push(Ember.Object.create({
"name": serialized[key].name,
"description": serialized[key].description,
"default": serialized[key]["default"],
"value": serialized[key].value,
"category": serialized[key].category
}));
}
//return objects;
return Ember.ArrayProxy.create({ content: objects });
},
//return JSON object
serialize: function (deserialized) {
var analyticsTemplate = {}, object;
for (var i = 0, len = deserialized.length; i < len; i++) {
object = deserialized[i];
analyticsTemplate[object.get('name')] = {"name": object.get('name'),
"description": object.get('description'),
"default": object.get('default'),
"value": object.get('value'),
"category": object.get('category')};
}
return analyticsTemplate;
}
});