Flutter, Day 01

Dear diary,
today I started to get into developing with flutter.

Why you ask?

Well, the promise of this language is, it will compile on just about any system you can imagine, deploy to Android, iOS, Linux, Mac, Web and Windows with just one code basis. Sounds good right?

How it went?

I landed flat out on my nose a couple of times and something that would have taken me mere minutes in any language I already know took me 6 hours today. But let’s start from the top.

After installing everything according to the official documentation at https://flutter.dev/ I went ahead and started a new project.

Creating a new project provides you with a couple of files you will need and a fully functioning demo app you can deploy and play around with. Also a lot of comments in the provided code so you know what is doing what.

I would have started with a simple “Hello World” but hey, counting clicks of a button (the demo app) also proves I did everything right with the installation.

Today’s goal

I started out with this project to write a little app, that can talk to a self design controller board for my air conditioning unit. That is a BIG, MASSIVE project and not a goal for a days work, but since I am planning on making the entire project open source, providing it in other languages than German might be nice for most users.

So the goal I set myself was this, create a button on the main screen, that will take over the function of the given increment one. The text on it will be loaded at runtime from a xml file in the desired language.

Step one

First up I quickly designed how I want my xml file to look like, what I ended up with is nodes stating the buttons and the attributes the text in the chosen language. Something like this:

<?xml version="1.0"?>
    <menus xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="menus.xsd">
      <btn1>
        <DE>Mein toller Knopf</DE>
        <EN>My great button</EN>
      </btn1>
    </menus>
    

Step two

Now I had to get this file into my app, to do this, I created a folder called “assets” in my project directory and added a couple of lines to the “pubspec.yaml” file to let the compiler know how to deal with this folder.

flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true
  assets: 
   - assets/menus.xml

I saved the file and got my first error message.

[testapp] flutter pub get
Error detected in pubspec.yaml:
Error on line 62, column 4: Expected a key while parsing a block mapping.
   ╷
62 │    assets:
   │    ^
   ╵
Please correct the pubspec.yaml file at /Users/wolfgang/Documents/Projekte/testapp/testapp/pubspec.yaml
exit code 1

I did correct the error, yaml files are very annoying when it comes to using the right amount of spaces. However the error didn’t go away.

It took me some time to figure out, that the output of VSCode is continuos and does not reload on rebuild. So I tried to get rid of an error I had already corrected. (If someone form the VSCode project should happen to read this, Timestamps for you messages would be great!)

Anyway, I figured it out and continued.

Step three

Now I had to read from that file, so this is where the actual coding begins.

Since there would be multiple buttons and I would have to execute that code whenever I wanted to get the correct label, it only made sense to put it into a function I could call from all files in my codebase. Expecting I would need more functions related to handling text, I added a new code file to the project called stringfunctions.dart

After a bit of web research I was happy with my code and put it in. What I ended up with looked something like this:

import 'dart:io';
import 'package:xml/xml.dart';

String getFileContents(String filename) {
  String tmpstr = "NoSuchFileOrDirectory";
  File tmpfile = File(filename);
  
  if (tmpfile.existsSync()) {
  tmpstr = tmpfile.readAsStringSync();
  }

  return tmpstr;
}
String getMenuText(String menu, String language) {
  String menutext = "";
  String filename = "assets/menus.xml";

  var menus = "";

  menus getMenuText("btn1", "DE");
  if (menus != "" && menus != "NoSuchFileOrDirectory") {
    final document = XmlDocument.parse(menus);

    final docret = document
        .findAllElements(menu)
        .map((node) => node.findElements(language).single.text);
    menutext = docret.elementAt(0);
  }
  return menutext;
}

I called it from main.dart and I got a lovely output, just not the one I expected.

It read: NoSuchFileOrDirectory

Step four

Here I went into a completely wrong direction, my assumption was the menus.xml was not copied to my app and that was why it couldn’t read it or that my file path just wasn’t right.

Let’s just say a little while later I figured it out, you can’t access an assets file like that, you need to do it completely different.

Step five

Somewhere in the documentation I stumbled onto the sentence “Flutter is asynchronous by design”, but I didn’t pay enough attention to this. Long story short, if you want to access your assets, you will have to do it in an async way.

This makes a lot of sense, I was only going to load a couple of bytes of text, a user won’t notice that. But if your UI tries loading a big image file, doing this synchronous would freeze the app completely. Not a good experience.

But also not what I planned for, so I wasted more time trying to figure out if there wasn’t a way to defy flutter and still do it synchronous. There wasn’t. If there is, I didn’t find it.

Thankfully I got a couple of pointers from the nice folk over at https:/fosstodon.org and finally gave up and implemented it as intended, asynchronous.

This is what I ended up with:

import 'package:xml/xml.dart';
import 'dart:async';
import 'package:flutter/services.dart' show rootBundle;

//Loads language texts for buttons and menus
//Menu = Node of menus.xml e.g. btn1
//Language = Handle for Language e.g. DE
Future<String> getMenuText(String menu, String language) async {
  String menutext = "Error Loading Element";
  String filename = "assets/menus.xml";

  var menus = "";

  menus = await loadAsset(filename);
  if (menus != "") {
    final document = XmlDocument.parse(menus);

    final docret = document
        .findAllElements(menu)
        .map((node) => node.findElements(language).single.text);
    menutext = docret.elementAt(0);
  }
  return menutext;
}

//Loads complete content of asset text files 
Future<String> loadAsset(filename) async {
  return await rootBundle.loadString(filename);
}

I already hear you asking: “Wait, what the hell is a Future?”

Futures are something new to me as well, what this means, is in the future there will be a result of the datatype within the <> brackets, in this case as string.

Futures have two states, uncompleted and completed. Once the async function is completed you can access it.

Step six

Now I had to figure out how to call my function, you can’t simply call an async function from a sync function.

To do this, I changed the main function of the app to an asynchronous one:

Future<void> main() async {
  runApp(const MyApp());
}

That step allows me to call async functions from the main interface. So far so good, but where do I call it?

My solution was to put everything I needed in the already available class “class _MyHomePageState extends State<MyHomePage>”

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  String _btn1 = ""; //String to display on button
  
  Future<void> _loadbtn1() async {
    final btn1text = await getMenuText("btn1", "DE");
    setState(() {
      _btn1 = btn1text;
    });
  }

 Widget build(BuildContext context) {

    _loadbtn1(); //Actual call to function
    return Scaffold(
.
.
.
.
.
.
.

And that was about it.

Today’s conclusion

Yes, that took me way longer than I expected, but given these are my first steps with flutter, that is more than okay.

I did not expect to dive head first into asynchronous programming on day one, but I am also glad I got it out of the way, I would have needed this later anyway.

All in all, I reached my goal, which is also great and I already have one of the functions done that will save me a lot of time later in the project.

The next step will be to give the user an option to choose the display language and save that choice to a file, load the choice on startup if the file exists.

I also want to take a look at error handling in flutter, wouldn’t want this to crash and freeze people to death, because they can’t turn off their air conditioning any more.

Anyway, didn’t go as planned, but was way less painful than anticipated, all well so far I guess.

TheWKBlog.com All Rights Reserved. Theme by 404 THEME.