Dear diary,
today I resumed my journey into Flutter and like every good story, this one starts with a disaster.
Not a Flutter related one, but I am getting ahead of myself.
I started like on any other day of developing, good coffee, plug my MacBook Air into my external monitor and open up VS-Code.
Quickly run a build to make sure I didn’t dream last time and everything still works.
However it didn’t work.
I got the strangest outputs about components missing to start the compiler.
I figured something in the OS went wrong and just rebooted the laptop.
To my astonishment, after the reboot my account was gone. Nice…. (Not)
I am not sure what went wrong but I do regular backups on the MacBook since it has become my main computer, so I decided to restore it from yesterdays backup since it worked back then.
But, what do you know, after the restore my account was still missing.
Since I did not want to put in the time to fix the Mac and get ahead instead, I decided to turn this into an opportunity and move my code over to my ubuntu machine and test if the App would build in linux. Since I also backup all my data to my server in addition to Apple backups, that took seconds.
Always have multiple Backups!!
And it did build! Works just fine. Quickly installed Android studio, built it for Android and, guess what, also works. Nice!
Today’s goal
I wanted to make sure, future users could actually choose the language the App would be displayed in, I already laid the foundation on day one by loading text from a multi language xml file, but getting a selection instead of hardcoding it certainly would help.
Step one
Since the App will have multiple windows the first thing I wanted to do was get a global variable that I could save the current language to. That was easily done.
I added a new file called “globals.dart” to the project, this will contain all my global variables.
It now only holds one variable:
String language = "DE";
Next I only need to include this in all the files I’ll be needing to access the language by putting this in the import section:
import './globals.dart' as globals;
That was easy, replaced the “DE” in all my functions with the variable and it worked like a charm.
Step two
Now what I needed was a second windows for the language chooser. So an Activity for you Android-Devs out there and a ViewController for you iOS folk.
Now remembering back to segues and having to align panels in screens for iOS I was very happy to see how easy this is in flutter.
To my “main.dart” I only had to add a couple lines of code:
class ChooseLanguage extends StatefulWidget {
const ChooseLanguage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<ChooseLanguage> createState() => _ChooseLanguageState();
}
class _ChooseLanguageState extends State<ChooseLanguage> {
String langDE = "";
String langEN = "";
String header = "";
Future<void> _loadheader() async {
final headertext = await getMenuText("langChoose", globals.language);
setState(() {
header = headertext;
});
}
Future<void> _loadlangDE() async {
final langDEtext = await getMenuText("langDE", globals.language);
setState(() {
langDE = langDEtext;
});
}
Future<void> _loadlangEN() async {
final langENtext = await getMenuText("langEN", globals.language);
setState(() {
langEN = langENtext;
});
}
Future<void> _langreload() async {
_loadheader();
_loadlangDE();
_loadlangEN();
}
@override
Widget build(BuildContext context) {
_langreload();
return Scaffold(
appBar: AppBar(
title: Text(header),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () {
globals.language = "DE";
_langreload();
},
child: Text(langDE),
),
ElevatedButton(
onPressed: () {
globals.language = "EN";
_langreload();
},
child: Text(langEN),
),
])));
}
}
So what do we have here, I created a new class, that represents my new “Window”, which in Flutter is called a screen. For this screen, I told it how I would want everything to be laid out so the bar on the top, telling the user what to do and the center as a column of buttons that are centered. One for the two languages now available.
Since I made “language” a global variable before, moving back and forth had the effect I desired, all screens in the chosen language.
Step three
Naturally I wouldn’t want a user to have to choose the language on every start of the App, thus I would need to save the choice somewhere. A file to be exact.
I am probably going to be writing to a couple of files in the future, so let’s pack all that in neat functions.
First we are going to need a path to save data to, according to the flutter documentation this is done by calling “getApplicationDocumentsDirectory”. To be completely honest I went by the documentation here and just adapted a little for my needs.
Future<String> _localPath() async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Nice, now we have a path to save to, all we need is a file, but I am going off the code provided by Flutter here, because I want to choose the filename myself, so I added a parameter to the function:
Future<File> _localFile(String filename) async {
final path = await _localPath();
return File('$path/$filename');
}
Okay, now I have an easy way to create a new file (name), next step write to it:
Future<File> fputs(String data, String filename) async {
File file = await _localFile(filename);
return file.writeAsString(data);
}
So here I pass the data I want to write and the filename, the function retrieves the correct path, and writes the data to the file. The function writeAsString is a replace content not append one as it turns out, which works fine for this use case, but I am going to have to look into how to append later on.
Next up, read from it:
Future<String> fgets(String filename) async {
try {
final file = await _localFile(filename);
final contents = await file.readAsString();
return contents;
} catch (e) {
return "";
}
}
So again, I just put the filename in and get the contents as a result of the function or empty if there is no file.
Step four
Now I only had to put the fgets and fputs functions into my main.dart and stet the globals.langauge variable to the content. Easy copy and paste.
Worked fine on both Android and Linux and I now can save the chosen language. Nice!
Conclusion
First up, don’t trust your Mac 😉
On a more serious note, I am very impressed by the documentation of flutter, almost all I needed I found within the official documentation and I did not stumble on one “deprecated” note in there. I also did not end up on stackoverflow or similar sites, this is not something you see often, well done!
Transferring a project to another computer was easy, just copy the folder. I was a bit afraid of this, since I needed to install various libraries so far and wasn’t sure if this would be saved to the project folder. But it does, which also explains, why this project is already 1.25 GB big, which is enormous, if you consider that a source code is only text files.
But I really do like this language, flutter for the UI is easy to understand an perfectly adapts to different sizes and dart is in syntax very close to C which makes it really easy for me to write and read.
I think the next step will be to clean up the code a bit as there are multiple things in my main.dart that are just code copies and also to finally add some error handling.
Also, getApplicationDocumentsDirectory in Linux puts the file into the users documents folder. That is not what I wanted to do and also dangerous, the file could be deleted or overwritten by another application. I will have to move this to a different location.
After I am done getting my Mac back in working order, I will have to see, how this performs on iOS.
If all works there as well, the basis of this project is laid and the actual work can begin.