dev1ce
dev1ce

Reputation: 1727

How to parse List of objects from firebase realtime database Flutter

I've been struggling a lot with this, not able to parse the data from my firebase realtime database using the following code. I have tried everything but I cannot do so. Below is code of necessary files. I'm using provider for state management.

My Firebase Realtime DB: Consist of 2 documents, 1 contains List of object as you can see, 2nd is just a single object. enter image description here

My Provider code:

class StockProvider with ChangeNotifier {
  List<ChartHistoricalData> _histChartData = [];
  List<ChartHistoricalData> get histChartData => _histChartData;

  Future<void> fetchHistoricalStockData(ref) async {
    ref.once().then((DataSnapshot data) {
      print(data.value['SBIN_Historical']);
      _histChartData = List<ChartHistoricalData>.from(data
          .value['SBIN_Historical']
          .map((x) => ChartHistoricalData.fromJson(x as Map<String, dynamic>)));
    notifyListeners();
    });
  }
}

The widget where I call:

class _SBINChartState extends State<SBINChart> {
  final fb = FirebaseDatabase.instance;
  @override
  Widget build(BuildContext context) {
    final ref = fb.reference();
    context.read<StockProvider>().fetchHistoricalStockData(ref);
    ...

My Model Class:

class ChartHistoricalData {
  DateTime? x; //DateTime
  double? open;
  double? high;
  double? low;
  double? close;

  ChartHistoricalData(
      {required this.x,
      required this.open,
      required this.high,
      required this.low,
      required this.close});

  ChartHistoricalData.fromJson(Map<String, dynamic> json) {
    x = DateTime.parse(json['date']);
    open = json['open'];
    high = json['high'];
    low = json['low'];
    close = json['close'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['date'] = this.x;
    data['open'] = this.open;
    data['high'] = this.high;
    data['low'] = this.low;
    data['close'] = this.close;
    return data;
  }
}

I'm getting the following error:

Reloaded 1 of 620 libraries in 1,111ms.
I/flutter (32700): [{date: 2021-01-01, high: 280.0, low: 274.4, close: 279.4, open: 274.9}, {date: 2021-01-04, high: 283.9, low: 277.75, close: 281.05, open: 281.85}, {date: 2021-01-05, high: 282.45, low: 277.0, close: 281.75, open: 278.05}, {date: 2021-01-06, high: 289.15, low: 281.4, close: 285.05, open: 283.0}, {date: 2021-01-07, high: 291.8, low: 287.0, close: 287.7, open: 289.0}, {date: 2021-01-08, high: 291.4, low: 285.2, close: 286.0, open: 290.1}, {date: 2021-01-11, high: 288.2, low: 279.6, close: 282.5, open: 288.0}, {date: 2021-01-12, high: 293.85, low: 277.9, close: 292.5, open: 280.0}, {date: 2021-01-13, high: 308.0, low: 294.5, close: 306.8, open: 296.0}, {date: 2021-01-14, high: 309.25, low: 303.8, close: 307.25, open: 306.7}, {date: 2021-01-15, high: 310.9, low: 301.3, close: 303.85, open: 306.8}, {date: 2021-01-18, high: 308.65, low: 292.2, close: 294.45, open: 303.5}, {date: 2021-01-19, high: 302.5, low: 296.4, close: 298.6, open: 297.65}, {date: 2021-01-20, high: 304.7, low: 296.85, close: 302.55, open: 298.8
E/flutter (32700): [ERROR:flutter/shell/common/shell.cc(103)] Dart Unhandled Exception: type '_InternalLinkedHashMap<Object?, Object?>' is not a subtype of type 'Map<String, dynamic>' in type cast, stack trace: #0      StockProvider.fetchHistoricalStockData.<anonymous closure>.<anonymous closure>
package:algoguru/providers/stock_provider.dart:15
E/flutter (32700): #1      MappedListIterable.elementAt (dart:_internal/iterable.dart:412:31)
E/flutter (32700): #2      ListIterator.moveNext (dart:_internal/iterable.dart:341:26)
E/flutter (32700): #3      new List.from (dart:core-patch/array_patch.dart:40:17)
E/flutter (32700): #4      StockProvider.fetchHistoricalStockData.<anonymous closure>
package:algoguru/providers/stock_provider.dart:13
E/flutter (32700): <asynchronous suspension>
E/flutter (32700): #5      StockProvider.fetchHistoricalStockData
package:algoguru/providers/stock_provider.dart:11
E/flutter (32700): <asynchronous suspension>
E/flutter (32700):

If anyone could help, I would really appreciate it, it's been long debugging...

Upvotes: 3

Views: 3231

Answers (3)

Victor Eronmosele
Victor Eronmosele

Reputation: 7696

You can cast the data.value to a Map<String, dynamic> and use it in the fetchHistoricalStockData method like below:

Future<void> fetchHistoricalStockData(ref) async {
    ref.once().then((DataSnapshot data) {
    Map<String, dynamic> dataValue = data.value as Map<String, dynamic>;

      _histChartData = List<ChartHistoricalData>.from(dataValue['SBIN_Historical']
          .map((x) => ChartHistoricalData.fromJson(x as Map<String, dynamic>)));
    notifyListeners();
    });
  }

Upvotes: 2

dev1ce
dev1ce

Reputation: 1727

Okay so finally! It is not a great solution but it did work for me. I don't know and i'm not sure about the data type which firebase_database return us, but by first json encoding snapshot instance data and then json decoding it solved the data type issue. The Code:

Future<void> fetchHistoricalStockData(ref) async {
    return ref.once().then((DataSnapshot data) {
      final cleanup = jsonDecode(jsonEncode(data.value)); // THIS IS THE ANSWER
      _histChartData = List<ChartHistoricalData>.from(cleanup['SBIN_Historical']
          .map((x) => ChartHistoricalData.fromJson(x as Map<String, dynamic>)));
      notifyListeners();
    });
  }

Upvotes: 1

Moaz El-sawaf
Moaz El-sawaf

Reputation: 3049

Problem: The problem is located in the method called fetchHistoricalStockData(ref), when you are calling ref.once() it is an Asynchronous method which would take some time to execute, you are awaiting the method but also you are providing .then(data) callback, so the problem is when the method ref.once() finished, the code inside .then(data and the code after ref.once() are begin executed together, so you are calling notify notifyListeners() before the data being parsed.

Solution: Move the call of notifyListeners() inside .then(data) callback after completely parsing the data then remove the await before ref.once() because no need for it ( the .then(data) callback is doing the same job ) and replace it with a return to directly return the feature of ref.once() and do await on it in the place where you are calling the method.

I hope it works, good luck.

Upvotes: 1

Related Questions