I want to upload a List of a custom class to the cloud firebase, but I get the error that my custom class is not a subtype of type <dynamic>. Does this happen because it is not possible to upload List of custom classes to firebase? Do I need to create e new collection which than works as the List or is there a other way to work around this problem?
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
//Class for the custom list
class CustomClass {
int id;
String content;
CustomClass({this.id, this.content});
CustomClass.fromMap(Map<String, dynamic> data) {
id = data['id'];
content = data['content'];
}
Map<String, dynamic> toMap() {
return {
'id': id,
'content': content,
};
}
}
//Model which I want to upload with the custom list
class CustomModel {
String name;
List<CustomClass> customList;
CustomModel();
CustomModel.fromMap(Map<String, dynamic> data) {
name = data['name'];
customList = data['customList'];
}
Map<String, dynamic> toMap() {
return {
'name': name,
'customList': customList,
};
}
}
//Page to upload the customModel with the customList
class UploadPage extends StatelessWidget {
CustomModel _customModel = CustomModel();
List<CustomClass> _customList = [CustomClass(id: 1, content: 'UserContent')];
#override
Widget build(BuildContext context) {
return Scaffold(
body: FlatButton(
child: Text('Upload'),
onPressed: () async {
_customModel.name = 'UserName';
_customModel.customList = _customList;
await Firestore.instance
.collection('CustomClass')
.add(_customModel.toMap());
}),
);
}
}
You can use dart: convert to convert your model object into json format i.e Map which you can upload to Firebase.
For this, I have renamed your toMap method to toJson & fromMap methods to fromJson. Also, I have added factory keyword before fromJson methods.
You should also override the toString method to print your objects for testing/debugging.
Your code should look something like this:
//Class for the custom list
class CustomClass {
int id;
String content;
CustomClass({this.id, this.content});
factory CustomClass.fromJson(Map<String, dynamic> data) {
return CustomClass(
id: data['id'],
content: data['content'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'content': content,
};
}
#override
String toString() {
return '${const JsonEncoder.withIndent(' ').convert(this)}';
}
}
//Model wich I want to upload with the custom list
class CustomModel {
String name;
List<CustomClass> customList;
CustomModel({
this.name,
this.customList,
});
factory CustomModel.fromJson(Map<String, dynamic> data) {
List<dynamic> list = data['customList'] ?? [];
final customList = list.map((e) => CustomClass.fromJson(e)).toList();
return CustomModel(
name: data['name'],
customList: customList,
);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'customList': customList?.map((e) => e.toJson())?.toList(),
};
}
#override
String toString() {
return '${const JsonEncoder.withIndent(' ').convert(this)}';
}
}
// Your build method:
CustomModel _customModel = CustomModel();
List<CustomClass> _customList = [CustomClass(id: 1, content: 'UserContent')];
#override
Widget build(BuilContext context) {
return Scaffold(
body: FlatButton(
child: Text('Upload'),
onPressed: () async {
_customModel.name = 'UserName';
_customModel.customList = _customList;
await Firestore.instance
.collection('CustomClass')
.add(_customModel.toJson());
}),
);
}
Related
I have a class name Todo
class TodoField {
static const createdTime = 'createdTime';
}
class Todo {
DateTime createdTime;
String title;
String id;
String description;
bool isDone;
Todo({
#required this.createdTime,
#required this.title,
this.description = '',
this.id,
this.isDone = false,
});
}
My data is saved in collection name: MyTodos. I want to add a collection in a list like List<Todo> _todo=[Todo...]
I'd advise creating a fromJson factory method to this Todo class, that way mapping the Firebase documents' data to a strongly-typed PODO (Plain Ol' Dart Object) is easier to manipulate in Flutter.
Your updated Todo class would look like:
class Todo {
DateTime createdTime;
String title;
String id;
String description;
bool isDone;
Todo({
#required this.createdTime,
#required this.title,
this.description = '',
this.id,
this.isDone = false,
});
factory Todo.fromJson(Map<String, dynamic> json) {
return Todo(
createdTime: json['createdTime'],
title: json['title'],
id: json['id'],
description: json['description'],
isDone: json['isDone']
);
}
}
Then, wherever you're pulling the Firebase data from your collection ** MyTodos**, you should do the mapping, as such:
QuerySnapshot snapshot = await FirebaseFirestore.instance.collection('MyTodos').get();
List<Todo> _todos = snapshot.docs.map((d) => Todo.fromJson(d.data())).toList();
Then you can do anything you want with your _todos collection. For example, inside a provided service, you can grab the data as follows:
class TodosProvider extends ChangeNotifier {
Future<List<Todo>> GetData() async {
QuerySnapshot snapshot = await FirebaseFirestore.instance.collection('MyTodos').get();
List<Todo> _todos = snapshot.docs.map((d) => Todo.fromJson(d.data())).toList();
return _todos;
}
}
Then in your widget's build method you can consume this provider as such:
#override
Widget build(BuildContext context) {
// obtain the provided service
// make sure this service is injected at the root via
// either a ChangeNotifierProvider or MultiProvider
var todos = Provider.of<TodosProvider>(context, listen: false);
return Scaffold(
body: FutureBuilder(
future: todos.GetData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// here consume your Todos
List<Todo> todos = snapshot.data as List<Todo>;
// put them in a list or whatever you want
}
return CircularProgressIndicator();
}
)
);
}
I need to add list of objects in firestore. When I save the item to firestore show errors but it doesn't save. This is the error Message: "Error: Invalid argument: Instance of 'MakeSentence'". I used getX state. I could only add two list with the below code:
LessonModel:
import 'make_sentence_model.dart';
class LessonModel {
LessonModel({
String? id,
String? enWord,
List<String>? wordMeaning,
List<MakeSentence>? makeSentence,
String? createdAt,}) {
_id = id;
_enWord = enWord;
_wordMeaning = wordMeaning;
_makeSentence = makeSentence;
_createdAt = createdAt;
}
LessonModel.fromJson(dynamic json) {
_id = json['id'];
_enWord = json['en_word'];
_wordMeaning = json['word_meaning'] != null ? json['word_meaning'].cast<String>() : [];
if (json['make_sentence'] != null) {
_makeSentence = [];
json['make_sentence'].forEach((v) {
_makeSentence?.add(MakeSentence.fromJson(v));
});
}
_createdAt = json['createdAt'];
}
String? _id;
String? _enWord;
List<MakeSentence>? _makeSentence;
String? _createdAt;
String? get id => _id;
String? get enWord => _enWord;
List<MakeSentence>? get makeSentence => _makeSentence;
String? get createdAt => _createdAt;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = _id;
map['en_word'] = _enWord;
if (_makeSentence != null) {
map['make_sentence'] = _makeSentence?.map((v) => v.toJson()).toList();
}
map['createdAt'] = _createdAt;
return map;
}
}
MakeSentence:
class MakeSentence {
MakeSentence({
String? nativeSentence,
String? enSentence,
}) {
_nativeSentence = nativeSentence;
_enSentence = enSentence;
}
MakeSentence.fromJson(dynamic json) {
_nativeSentence = json['native_sentence'];
_enSentence = json['en_sentence'];
}
String? _nativeSentence;
String? _enSentence;
String? get nativeSentence => _nativeSentence;
String? get enSentence => _enSentence;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['native_sentence'] = _nativeSentence;
map['en_sentence'] = _enSentence;
return map;
}
}
HomeService:
class HomeService {
final FirebaseFirestore _fireStore = FirebaseFirestore.instance;
Future<LessonModel> saveLesson(LessonModel lesson) async {
DocumentReference docRef = _fireStore
.collection("save_word")
.doc(lesson.userID)
.collection("lesson_list")
.doc();
await docRef.set({
"id": docRef.id,
"en_word": lesson.enWord,
"make_sentence": lesson.makeSentence,
"createdAt": lesson.createdAt
});
return lesson;
}
}
HomeController:
class HomeController extends GetxController {
late User user;
var lessonList = [].obs;
final HomeService homeService = HomeService();
#override
void onInit() async {
super.onInit();
user = Get.arguments;
}
void saveLesson({
String? enWord,
List<MakeSentence>? makeSentence,
String? createdAt,
}) async {
try {
LessonModel lModel = LessonModel(
enWord: enWord,
makeSentence: makeSentence,
createdAt: createdAt,
);
await homeService.saveLesson(lModel);
Get.showSnackbar(
const GetSnackBar(title: 'Success', message: 'Contact saved'));
} catch (e) {
debugPrint("Error: ${e.toString()}");
Get.showSnackbar(
const GetSnackBar(title: 'Error', message: 'something went wrong'));
}
}
}
HomePage:
class HomePage extends GetView<HomeController> {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
List<MakeSentence> makeSentence= [
MakeSentence(enSentence: "English",nativeSentence: "Native")
];
return Scaffold(
backgroundColor: Color(0xFF47A1A0),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.saveLesson(
enWord: "Slumber",
makeSentence: makeSentence,
createdAt: DateTime.now().toString(),
);
},
child: Icon(
Icons.add,
color: Color(0xFF47A1A0),
size: 36,
),
backgroundColor: Colors.white,
tooltip: 'Add',
elevation: 5,
splashColor: Colors.white,
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}
How to add unique value to each expandable tile to make the FAQ look like this?
Took my code from this link: https://medium.com/aubergine-solutions/how-to-create-expansion-panel-list-in-flutter-2fba574366e8
I want to add my own unique headerValue and expandedValue and not let it just take up a standard number like
headerValue: 'Book $index',
expandedValue: 'Details for Book $index goes here'
I'll probably be using this class and not theirs:
class FAQ {
FAQ({
this.id,
this.expandedValue,
this.headerValue,
});
int id;
String expandedValue;
String headerValue;
}
You can create your custom class which can return a list of your questions. In your Item class you need to define functions Item.fromJson(dynamic json) which will return you Map or your items. Then you can create one more function where you will pass your actual data to your class Item and it will return you Map so you can use in your widgets.
Here is the full code and working
class Item {
final String id;
final String expandedValue;
final String headerValue;
Item(
this.id,
this.expandedValue,
this.headerValue,
);
factory Item.fromJson(dynamic json) {
return Item(
"${json['id']}", "${json['expandedValue']}", "${json['headerValue']}");
}
}
var mylist = [
{"id": "1", "headerValue": "question 1", "expandedValue": "answer 1"},
{"id": "2", "headerValue": "question 2", "expandedValue": "answer 2"}
];
getFeedbackList() {
return mylist.map((json) => Item.fromJson(json)).toList();
}
class ExpansionItems extends StatefulWidget {
const ExpansionItems({Key? key}) : super(key: key);
#override
State<ExpansionItems> createState() => _ExpansionItemsState();
}
class _ExpansionItemsState extends State<ExpansionItems> {
final List<Item> _data = getFeedbackList();
#override
Widget build(BuildContext context) {
print(_data);
return SingleChildScrollView(
child: Container(
child: _buildPanel(),
),
);
}
Widget _buildPanel() {
return ExpansionPanelList.radio(
initialOpenPanelValue: 2,
children: _data.map<ExpansionPanelRadio>(
(Item item) {
return ExpansionPanelRadio(
value: item.id,
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text(item.headerValue),
);
},
body: ListTile(
title: Text(item.expandedValue),
),
);
},
).toList(),
);
}
}
You can try above code here
My small app, is getting list of users from JSON link then store it in the List, I wanna this list into usersCollection collection ref of firestore
my code
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:yat_flutter_app/main.dart';
import 'usersList.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
CollectionReference usersCollection =
FirebaseFirestore.instance.collection('users');
Future<List<User>> getUsers() async {
var data = await http
.get("https://www.json-generator.com/api/json/get/bYKKPeXRcO?indent=2");
var jasonData = json.decode(data.body);
List<User> users = [];
for (var i in jasonData) {
User user = User(i["index"], i["about"], i["name"], i["picture"],
i["company"], i["email"]);
users.add(user);
}
return users;
}
#override
Widget build(BuildContext context) {
List<User> usersList = getUsers() as List<User>;
return Container(
child: Column(
children: [
FutureBuilder(
future: getUsers(),
builder: (BuildContext context, AsyncSnapshot asyncSnapshop) {
if (asyncSnapshop.hasData) {
return Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: asyncSnapshop.data.length,
itemBuilder: (BuildContext context, int index) {
return Card(
elevation: 5,
color: Colors.cyan[50],
child: ListTile(
trailing: Icon(Icons.share),
title: Text(asyncSnapshop.data[index].name, style: TextStyle(fontFamily: 'Tahoma',fontSize: 20,fontWeight: FontWeight.bold),),
leading: CircleAvatar(
backgroundImage: NetworkImage(
asyncSnapshop.data[index].picture +
asyncSnapshop.data[index].index.toString() +
".jpg"),
),
subtitle: Text(asyncSnapshop.data[index].email,style: TextStyle(fontFamily: 'Tahmoma',fontSize: 18),),
onTap: (){
Navigator.push(context, new MaterialPageRoute(builder: (context)=>
detailsPage(asyncSnapshop.data[index])
));
},
onLongPress: ()=>
Fluttertoast.showToast(
msg: asyncSnapshop.data[index].name,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.green[900],
textColor: Colors.white,
fontSize: 16.0
),
),
);
}),
);
} else {
return Text("Loading, please wait...");
}
},
),
ElevatedButton(
child: Text('Save data'),
onPressed: () => {
usersCollection.add(getUsers()); // here's I am trying to add the result of getUsers into usersCollection
}),
],
),
);
}
}
To push an object to Firestore you need to convert your object to map.
You can just add this function to your class:
Map<String, dynamic> toMap() {
return {
'field1': value1,
'field2': value1,
};
}
To push a List , you need to convert all objects to map, you can do it with following method:
static List<Map> ConvertToMap({List myList }) {
List<Map> steps = [];
myList.forEach((var value) {
Map step = value.toMap();
steps.add(step);
});
return steps;
}
Or simply , see how to convert List to Map
I hope it will be useful
To push this list to Firestore you need to fromJson and toJson methods in your model class
factory User.fromJson(Map<String, dynamic> data){
return User(
index: data['index'] as int,
about: data['about'] as String,
name: data['name'] as String,
picture: data['picture'] as String,
company: data['company'] as String,
email: data['email'] as String );
}
Map<String, dynamic> toJson(){
return {
"index": index,
"about" : about,
"name" : name,
"picture" : picture,
"company" : company,
"email" : email,
};
}
instead that I would like to suggest using json_serializable library
then you need to do some changes in your future method like this
getUsers().then((users) {
// add users to map
});
and then you can use fromJson method to push it to firestore database
Firebase realtime database and firestore are no SQL databases where data will be stored in Parent child relation or Tree structure.
For you to store list of data you can convert your list into Map
Map can be initialised as follows
Map<String, String> toMap() {
return {
'Fruit': "Mango",
'Flower': "Lotus",
'Vegetable': "Potato",
};
}
After you have Map you can set value to the firestore. You can use the below code to set value
Map<String, Object> city = new Map<>();
//Loop through your list and load Map (City) values
db.collection("cities").document("LA").set(city)
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "DocumentSnapshot successfully written!");
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "Error writing document", e);
}
});
You can convert List of items to map using this
Java: How to convert List to Map
I want to cache two lists that got from Firebase to use to later when the user is offline
This is the full code for my list display screen -
import 'package:flutter/material.dart';
import 'package:naamaa/calculations/name-list-calc.dart';
List namesList = List();
List meaningsList = List();
class NameList extends StatefulWidget {
#override
_NameListState createState() => _NameListState();
}
class _NameListState extends State<NameList> {
Future<String> getPosts() async {
var names = await NameListCalc().nameListCalc();
namesList.addAll(names[0]);
meaningsList.addAll(names[1]);
String s = 'test';
return s;
}
#override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: getPosts(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Scaffold(
resizeToAvoidBottomPadding: false,
body: ListView.builder(
padding: EdgeInsets.zero,
itemBuilder: (context, position) {
return Row(
children: <Widget>[
Container(
width: 100,
child: Text(namesList[position]),
),
Container(
child: Text(meaningsList[position]),
)
],
);
},
itemCount: namesList.length,
),
);
} else {
return Text(':(');
}
},
);
}
}
I want to cache namesList and meaningsList for later use.
If someone can help it would be great :)
I didn't get complete requirement by your question description but you can use shared_preferences library to store the data list as following
Add following line pubspec.yaml
dependencies:
flutter:
sdk: flutter
shared_preferences:
You can use this example and add more utility methods as per you requirement.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() async {
AppConfig.init(() {
runApp(MyApp());
});
}
class CustomModel {
int id;
String name;
CustomModel({this.id, this.name});
factory CustomModel.fromJson(Map<String, dynamic> json) {
return CustomModel(id: json["id"], name: json["name"]);
}
Map<String, dynamic> toJson() => {"id": id, "name": name};
#override
String toString() {
return "id: $id, name: $name";
}
}
class AppConfig {
static Future init(VoidCallback callback) async {
WidgetsFlutterBinding.ensureInitialized();
await SharedPreferenceUtils.init();
callback();
}
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class SharedPreferenceUtils {
static SharedPreferences prefs;
static init() async {
prefs = await SharedPreferences.getInstance();
// storing lists
await putStringList("m_list", ["abc", "def"]);
await putObjectList("data",
[CustomModel(id: 1, name: "Bob"), CustomModel(id: 2, name: "Alice")]);
}
static Future<bool> putStringList(String key, List<String> list) async {
return prefs.setStringList(key, list);
}
static List<String> getStringList(String key) {
return prefs.getStringList(key);
}
static Future<bool> putObjectList(String key, List<Object> list) async {
if (prefs == null) return null;
List<String> _dataList = list?.map((value) {
return json.encode(value);
})?.toList();
return prefs.setStringList(key, _dataList);
}
static List<T> getObjList<T>(String key, T f(Map v),
{List<T> defValue = const []}) {
if (prefs == null) return null;
List<Map> dataList = getObjectList(key);
List<T> list = dataList?.map((value) {
return f(value);
})?.toList();
return list ?? defValue;
}
static List<Map> getObjectList(String key) {
if (prefs == null) return null;
List<String> dataList = prefs.getStringList(key);
return dataList?.map((value) {
Map _dataMap = json.decode(value);
return _dataMap;
})?.toList();
}
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(SharedPreferenceUtils.getStringList("m_list").toString()),
Text(SharedPreferenceUtils.getObjList<CustomModel>(
"data", (v) => CustomModel.fromJson(v)).toString()),
],
),
),
),
);
}
}
You don't need to store the lists in init() as it's done for this example. You can also pass data from one widget to others in multiple ways and if you are looking for state management then you can use BLOC or providers.