I am trying to retrieve data from a Realtime database in Firebase to Flutter. The data should be parsed to be used in the building of a listview inside a future builder. However, after I execute the code I got an error that displayed on the Emulator screen. My understanding is that there is a type mismatch inside the code of firebaseCalls method. Below is my code Main.dart, data model, Firebase data, and Error Message. Any help to figure out the issue is appreciated. Thanks in advance!
Main.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'datamodel.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_core/firebase_core.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final ref = FirebaseDatabase.instance.ref();
Future<List<Menu>> firebaseCalls(DatabaseReference ref) async {
DataSnapshot dataSnapshot = await ref.child('Task').get();
String? jsondata =dataSnapshot.value as String?; // just in case String is not working
//String jsondata = dataSnapshot.children;// value;//[0]['Task'];// should be dataSnapshot.value
// Decode Json as a list
final list = json.decode(jsondata!);// as List<dynamic>;
return list.map((e) => Menu.fromJson(e)).toList();
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
//drawer: _drawer(data),
appBar: AppBar(
title: const Text('الصف السادس العلمي'),
),
body: FutureBuilder(
future: firebaseCalls(ref), // async work
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text('Press button to start');
case ConnectionState.waiting:
return new Text('Loading....');
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) =>
_buildTiles(snapshot.data[index]),
);
}
} // builder
)
)
);
}
////////////////////////////////////////////
Widget _buildTiles(Menu list) {
if (list.subMenu?.length == 0)
return new ListTile(
leading: Icon(list.icon),
title: Text(
list.name!,
style: TextStyle(
fontSize: list.font?.toDouble(), fontWeight: FontWeight.bold),
),
onTap: () => debugPrint("I was clicked"),
);
return new ExpansionTile(
leading: Icon(list.icon),
title: Text(
list.name!,
style: TextStyle(
fontSize: list.font?.toDouble(), fontWeight: FontWeight.bold),
),
children: list.subMenu!.map(_buildTiles).toList(),
);
}//_buildTiles
}
datamodel.dart
import 'package:flutter/material.dart';
class Menu {
String? name; // I added ?
IconData? icon;// I added ?
int? font;// I added ?
List<Menu>? subMenu= [];// I added ?
Menu({this.name, this.subMenu, this.icon,this.font});
Menu.fromJson(Map<String, dynamic> json) {
name = json['name'];
font = json['font'];
icon = json['icon'];
if (json['subMenu'] != null) {
//subMenu?.clear(); // I added ? it also recomand using !
json['subMenu'].forEach((v) {
//subMenu?.add(new Menu.fromJson(v));
subMenu?.add(Menu.fromJson(v));
});
}
}
}
Database:
Error message:
The problem is here:
DataSnapshot dataSnapshot = await ref.child('Task').get();
String? jsondata =dataSnapshot.value as String?;
If we look at the screenshot of the database you shared, it's clear that the value under the /Task path is not a string. It is in fact an entire object structure, which means you get back a Map<String, Object> from dataSnapshot.value. And since that's not a String, you get the error that you get.
The proper way to get the value of the entire Task node is with something like:
Map values = dataSnapshot.value;
And then you can get for example the name with:
print(values["name"]);
Alternatively you get get the child snapshot, and only then get the string value from it, with something like:
String? name = dataSnapshot.child("name")?.value as String?;
Related
I get the error below after modifying my home page on my app to the code as shown below, I get the error in my object_patch.dart file. I don't know how that can be resolved. what could I have done wrong?
Exception has occurred.
NoSuchMethodError (NoSuchMethodError: The method '[]' was called on null.
Receiver: null
Tried calling: ) (see image)
import 'package:awsome_app/drawer.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class Homepage extends StatefulWidget {
const Homepage({Key? key}) : super(key: key);
#override
State<Homepage> createState() => _HomepageState();
}
class _HomepageState extends State<Homepage> {
//Functions here now
// var myText = "This is a function";
// TextEditingController _nameController = TextEditingController();
var url = "https://jsonplaceholder.typicode.com/photos";
var data;
#override
void initState() {
// TODO: implement initState
super.initState();
fetchData();
}
fetchData() async {
var res = await http.post(Uri.parse(url));
data = jsonDecode(res.body);
setState(() {});
;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromARGB(204, 255, 249, 249),
appBar: AppBar(title: const Text("Welcome to Flutter App")),
body: data != null
? ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text(data[index]["title"]),
);
},
itemCount: data.length,
)
: Center(
child: CircularProgressIndicator(),
)
);
}
}```
GET https://jsonplaceholder.typicode.com/photos will return a list of photos.
Replace
var res = await http.post(Uri.parse(url));
with
var res = await http.get(Uri.parse(url));
so It's my first time using API on flutter I just take the tutorial copy paste and change some line for the test I just want to learn more about API's so don't blame me if I made a dumb mistake or something like .
I try to get a title from the APi and get this error if somoene can help that will be really great.
Future<Album> fetchAlbum() async {
final response = await http
.get(Uri.parse('https://api.mangadex.org/manga'));
if (response.statusCode == 200) {
return Album.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load album');
}
}
class Album {
final int year;
final String title;
const Album({
required this.year,
required this.title,
});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
year: json['year'],
title: json['title'],
);
}
}
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late Future<Album> futureAlbum;
#override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.title);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
actually what goes wrong here is that json response you are getting back from the API doesn't contain a year or title fields. So, both json['year'] and json['title'] will be null. And since you are assigning them to Album.year and Album.title which are not nullable, the whole app breaks.
I tried calling the API URI, and found that the JSON structure is a bit different.
You might wan this code for your fromJson method for it to work.
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
year: json['data'][0]['attributes']['year'],
title: json['data'][0]['attributes']['title']["en"],
);
}
Please note that json['data'] is actually an array of albums, in this code we're just fetching ONLY the first element inside that array.
I added my existing database.db file to my project with sqflite. No errors encountered, everything works fine, but... Flutter debug console says:
Restarted application in 772ms.
════════ Exception caught by widgets library ═══════════════════════════════════
The following NoSuchMethodError was thrown building FutureBuilder<List<Countries>>(dirty, state: _FutureBuilderState<List<Countries>>#d0317):
The getter 'length' was called on null.
Receiver: null
Tried calling: length
The relevant error-causing widget was
FutureBuilder<List<Countries>>
When the exception was thrown, this was the stack
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
#1 _HomeScreen.buildBody.<anonymous closure>
#2 _FutureBuilderState.build
#3 StatefulElement.build
#4 ComponentElement.performRebuild
...
════════════════════════════════════════════════════════════════════════════════
I/flutter (14052): Opening existing database
Here is my model Country.dart :
class Countries {
int countryId;
String countryName;
String countryImageURL;
//Constructor
Countries({this.countryId, this.countryName, this.countryImageURL});
// Extract a Product Object from a Map Oject
Countries.fromMap(Map<String, dynamic> map) {
countryId = map['country_id'];
countryName = map['country_name'];
countryImageURL = map['image'];
}
Map<String, dynamic> toMap() {
var map = <String, dynamic>{
'country_name': countryName,
'image': countryImageURL
};
return map;
}
}
Here is my database_helper.dart file:
import 'dart:async';
import 'dart:io';
import 'package:city_travel_guide/model/Country.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'dart:typed_data';
import 'package:flutter/services.dart';
class DbHelper {
static Database _db;
Future<Database> get db async {
if (_db != null) {
return _db;
} else {
_db = await initDb();
return _db;
}
}
initDb() async {
var dbFolder = await getDatabasesPath();
String path = join(dbFolder, 'app.db');
var exists = await databaseExists(path);
if (!exists) {
// Should happen only the first time you launch your application
print("Creating new copy from asset");
// Make sure the parent directory exists
try {
await Directory(dirname(path)).create(recursive: true);
} catch (_) {}
// Copy from asset
ByteData data = await rootBundle.load(join("assets", "example.db"));
List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
// Write and flush the bytes written
await File(path).writeAsBytes(bytes, flush: true);
} else {
print("Opening existing database");
}
// open the database
return await openDatabase(path);
}
Future<List<Countries>> getCountries() async {
var dbClient = await db;
var result = await dbClient.query('Country', orderBy: 'countryId');
return result.map((data) => Countries.fromMap(data)).toList();
}
Here is my main.dart file:
import 'package:city_travel_guide/data/database_helper.dart';
import 'package:city_travel_guide/model/Country.dart';
import 'package:flutter/material.dart';
import 'widgets/maindrawer.dart';
import 'pages/search.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'City Travel Guide',
theme: ThemeData.dark(),
debugShowCheckedModeBanner: false,
home: MyHome());
}
}
class MyHome extends StatefulWidget {
#override
_HomeScreen createState() => _HomeScreen();
}
class _HomeScreen extends State<MyHome> {
List<Countries> countries;
final dbHelper = DbHelper();
#override
void initState() {
dbHelper.initDb();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'City Travel Guide',
style: Theme.of(context).primaryTextTheme.headline6,
),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.search),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SearchScreen()),
);
}),
IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}),
],
),
drawer: Drawer(child: MainDrawer()),
body: buildBody(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {},
));
}
buildBody() {
return FutureBuilder<List<Countries>>(
future: dbHelper.getCountries(),
builder: (context, snapshot) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(title: Text(snapshot.data[index].countryName));
},
);
});
}
}
How can I list items on my asset database and view it in application?
FutureBuilder is an asynchronous request. Always check that snapshot has data before building your list.
do:
buildBody() {
return FutureBuilder<List<Countries>>(
future: dbHelper.getCountries(),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data.length > 0) // This ensures that you have at least one or more countries available.
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(title: Text(snapshot.data[index].countryName));
},
);
else if (snapshot.hasData && snapshot.data.length == 0)
return Center(child:Text("There are no countries available"));
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).primaryColor),
)); // This would display a loading animation before your data is ready
});
}
Its to easy you must check if there is a data comming from the future or not before using the snapshot.data.lenght because if the snapshot.data is null(the opperation not finished yet) then lenght was calling on null so you must do it
The correct code
buildBody() {
return FutureBuilder<List<Countries>>(
future: dbHelper.getCountries(),
builder: (context, snapshot) {
if(snapshot.hasdata&&snapshot.data.runtimetype==List){
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(title: Text(snapshot.data[index].countryName));
},
);
}else{
return Proggresindicator()//or any loading widgets
}
});
}
}
and you can add check for any execption happens during the future
I am completly new to Flutter and Stackoverflow. This is my first question to be in fact so please forgive me if I totaly fail at asking this question. I am trying to make a simple Flutter app that provides a ListView of questions and a checkbox beside each. The user can then choose which question they want to answer. My problem is that when the user checks any of the checkboxes then all get checked and vise versa. The questions themselves are retrieved from a backendless database. The code below is what i have so far. I would really appreciate any help anyone can provide me.
import 'package:flutter/material.dart';
class Questions extends StatefulWidget {
final List<Map> questionList;
Questions(this.questionList);
#override
_QuestionsState createState() => _QuestionsState();
}
class _QuestionsState extends State<Questions> {
bool _questionSelected = true;
Widget _buildQuestionItem(BuildContext context, int index) {
return ListTile(
title: Text(widget.questionList[index]['question']),
trailing: Checkbox(
value: _questionSelected,
onChanged: (bool val){
setState(() {
_questionSelected = val;
});
},
),
);
}
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.all(10),
itemBuilder: _buildQuestionItem,
itemCount: widget.questionList.length,
);
}
}
UPDATED:
Thankful for Mohammed Ashab Uddin suggestions I feel that I am close to getting this thing to work but I am still getting an error
"RangeError (index): Invalid value: Valid value range is empty: 0"
I think I should have posted the main.dart code where I set the value of the questionList perhaps it is an order of code execution that causes this error so please find my code for main.dart below in hopes it would help in figuring out this issue.
import 'package:flutter/material.dart';
import 'package:backendless_sdk/backendless_sdk.dart';
import 'package:flutter/rendering.dart';
import 'questions.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'RT Database Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Questions'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
static const String API_HOST = "https://api.backendless.com";
static const String APP_ID = "<APP_ID>";
static const String ANDROID_APP_KEY = "<ANDROID_APP_KEY>";
static const String IOS_APP_KEY = "<IOS_APP_KEY>";
IDataStore<Map> questionsStore = Backendless.data.of('Questions');
List<Map> questionsList = [];
var _questionSelected = false;
#override
void initState() {
super.initState();
_initBackendless();
_enableRealTime();
getQuestions();
}
void _initBackendless() {
Backendless.setUrl(API_HOST);
Backendless.initApp(APP_ID, ANDROID_APP_KEY, IOS_APP_KEY);
}
void _enableRealTime() {
EventHandler<Map> rtHandlers = questionsStore.rt();
rtHandlers.addCreateListener((question) {
setState(() {
questionsList = List.from(questionsList);
questionsList.add(question);
});
});
rtHandlers.addUpdateListener((question) {
setState(() {
questionsList = List.from(questionsList
.map((m) => m['objectId'] == question['objectId'] ? question : m));
});
});
rtHandlers.addDeleteListener((question) {
setState(() {
questionsList = List.from(questionsList);
questionsList.removeWhere((m) => m['objectId'] == question['objectId']);
});
});
}
void _selectQuestion(bool newValue) {
setState(() {
_questionSelected = newValue;
});
}
void getQuestions() {
DataQueryBuilder queryBuilder = DataQueryBuilder()
..pageSize = 100
..sortBy = ['created'];
questionsStore
.find(queryBuilder)
.then((response) => setState(() => questionsList = response));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("My Life History"),
),
body: FractionallySizedBox(
heightFactor: 0.5,
child: Questions(questionsList),
),
);
}
}
The variable _questionSelected is a global variable. All the checkbox widgets are using this variable as the value. Therefore, when the variable changes on the onChanged() function, all the values are also changed to the value of _questionSelected.
In this case, you need to keep track of all the values of the checkbox widget. So, you should use an array rather than a single variable.
What I usually do is, create a new list that will contain only the selected elements.
Remove an element if it is not selected and add an element if it is selected.
//generate a list of false values with the length of questionList
List<bool> _questionSelected;
initState(){
_questionSelected = List<bool>.filled(questionList.length, false, growable: true);
super.initState();
}
Widget _buildQuestionItem(BuildContext context, int index) {
return ListTile(
title: Text(widget.questionList[index]['question']),
trailing: Checkbox(
value: _questionSelected[index],
onChanged: (bool val){
setState(() {
_questionSelected[index] = val;
});
},
),
);
}
I have used the flutter cookbook to create a Future in order to convert a single Map into a card. However I want to do this with a list of Maps. I know I need to create a list and then use the futurebuilder to return a list view, but I am unsure how to add to the apitest() method to add each Map to a list to then convert. Any help is much appreciated.
This is main.dart:
import 'package:flutter/material.dart';
import './viewviews.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'API TEST',
theme: new ThemeData(
primarySwatch: Colors.purple,
backgroundColor: Colors.black,
),
home: CardPage(),
);
}
}
This is my class to construct the object:
import 'package:flutter/material.dart';
class Post {
final String appName;
final String brandName;
final int views;
Post(
{#required this.appName, #required this.brandName, #required this.views});
factory Post.fromJson(Map<String, dynamic> json) {
return (Post(
views: json['Views'],
brandName: json['brandname'],
appName: json['appname']));
}
}
and finally the code I am using to make the api call and use the futurebuilder:
import 'dart:async';
//final List<Post> cardslist = [];
class CardPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(title: Text('API TEST')),
body: ListView.builder(
itemBuilder: cardbuilder,
itemCount: 1,
));
}
}
Future<Post> testapi() async {
final apiresponse =
await http.get('https://myriadapp-55adf.firebaseio.com/Views.json');
print(apiresponse.body);
return Post.fromJson(json.decode(apiresponse.body));
}
Widget cardbuilder(BuildContext context, int index) {
return Container(
margin: EdgeInsets.all(20.0),
child: FutureBuilder<Post>(
future: testapi(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.appName);
} else if (snapshot.hasError) {
return Text(snapshot.hasError.toString());
}
return Center(child: CircularProgressIndicator());
}));
}
I have commented out the code to create the list but I know I will need this, I just need to decode the json to add a list of Maps to it and then use Futurebuilder to return the listview. Thank you.
Your json doesn't actually return a json array, it actually returns a map with arbitrary key names like View1, so we'll iterate that map. You need to hoist your FutureBuilder up to the point where you can deal with the whole json at once, so at the page level. So CardPage becomes
class CardPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('API TEST')),
body: FutureBuilder<Map>(
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final Map<String, dynamic> data = snapshot.data;
final List<Container> cards = data.keys
.map((String s) => makeCard(Post.fromJson(data[s])))
.toList();
return ListView(
children: cards,
);
} else if (snapshot.hasError) {
return Text(snapshot.hasError.toString());
}
return const Center(
child: CircularProgressIndicator(),
);
},
),
);
}
Container makeCard(Post post) {
return Container(
margin: EdgeInsets.all(20.0),
child: Text(post.appName),
);
}
Future<Map> fetchData() async {
final apiresponse =
await http.get('https://myriadapp-55adf.firebaseio.com/Views.json');
print(apiresponse.body);
return json.decode(apiresponse.body);
}
}