skip to Main Content

A flutter newbie, I’m trying to get my app to take values from a local json file and render them on-screen.

The logic isn’t waiting for the class constructor to populate the relevant string variable before rendering the app on screen.

Here’s the code that illustrates my problem.

First, my main.dart file:

import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'sampleDataClass.dart';
import 'package:provider/provider.dart';

String assetFilePath = 'assets/basicData.json';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  FlutterError.onError = (details) {
    FlutterError.presentError(details);
    if (kReleaseMode) exit(1);
  };

  runApp(const MyApp());
}

class MyApp extends StatefulWidget {

  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyAppState(),
      child: const MaterialApp(
        title: "Sample screen",
        home: MyHomePage(),
      )
    );
  }
}

class MyAppState extends ChangeNotifier {
  SampleDataClass current = SampleDataClass(assetFilePath);
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});


  @override
  Widget build(BuildContext context) {
    var myAppState = context.watch<MyAppState>();

    var myAppBasicData = myAppState.current;

    return Scaffold(
        appBar: AppBar(
          backgroundColor: Colors.indigo,
          foregroundColor: Colors.amberAccent,
          title: const Text("This is the App Bar"),
          elevation: 10,
        ),
        body: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(children: [
                Expanded(
                    child: Container(
                        color: Colors.blueGrey,
                        padding: const EdgeInsets.all(10),
                        child: (
                            Text(myAppBasicData.language,
                                style: const TextStyle(
                                  color: Colors.white,
                                ))))),
              ]),
            ]
        ),
    );
  }
}

Here is my SampleDataClass.dart file:

import 'dart:core';
import 'dart:convert';
import 'package:flutter/services.dart' show rootBundle;

class SampleDataClass {
  String classFilePath = "";

  String language = "not populated";
  String batteryName = "not populated";

  SampleDataClass(filePath) {
    rootBundle.loadString(filePath).then((jsonDataString) {
      Map classDataMap = jsonDecode(jsonDataString);
      language = classDataMap['language'];
      print(language);
      batteryName = classDataMap['batteryName'];
      print(batteryName);
    });
  }

  Map<String, dynamic> toJson() => {
    'language': language,
    'batteryName': batteryName,
  };
}

And here’s my pubspec.yaml file:

name: samples
description: A new Flutter project.

environment:
  sdk: '>=2.18.6 <3.0.0'

  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  provider: ^4.1.1

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true
  assets:
    - assets/basicData.json

Finally, my basicData.json file looks like this:

{
  "language": "English",
  "batteryName": "Exercise 32"
}

The print statements in the sampleDataClass class work fine but as you can see if you run the code (from the command line using "flutter run –no-sound-null-safety"), the app renders the initial values of the variables before the class constructor assigns the json values to them.

What I expected was the class constructor to complete writing the relevant json values to the class members before handing back the class instance to the build widget in main.dart to render the app on-screen.

I realise that this is a very elementary question, but it is a key pending learning task for me!

Thank you all very much in advance!

2

Answers


  1. it looks like rootBundle is an async function, you have to wait till it finishes then you notify the UI to render with new data,

    How to handle this :

    • you should have 3 states loading state , error state , loaded state ,

    • create an init(path) function in SampleDataClass class that returns SampleDataClass instance.

    • Also, in the init() function at the beginning you have to change the state to loading state then when you get the data you set the state to loaded state, then notify listeners this will help the screen to know which state the page is in.

    • in the screen , call didChangeDependencies() and inside it, call your Provider => init function ( current = contecxt.read<YPUR_PROVIDER>().init(path); )

    • therefore the current object is being initialized, the page is showing a loader, and once the initialization is done, the provider will notify the page and it will change the loading view to the loaded view (your current view).

    Another Tip::
    your app will close whenever there is an error, it is not a good practice, since flutter will through non-fatal exception

     FlutterError.onError = (details) {
    FlutterError.presentError(details);
    if (kReleaseMode) exit(1);
    
    Login or Signup to reply.
  2. There is a choice of two excellent solutions to my problem with very little effort and no need for any change management (yet).

    Indeed, so similar is the problem described that I probably should have found it before I posted this query here. As a newbie in this discipline, I clearly didn’t conduct my search with the appropriate key words; for that I apologise!

    Thanks very much to everyone for your patience.

    The two solution options (which even I was able to follow) can be found here:

    Calling an async method from a constructor in Dart

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search