A Flutter Roundtable with TribalScale’s Engineers
By: Tribalscale Inc.
Tags: Android, Flutter, Roundtable, Software Development, Agile

TribalScale holds weekly Learning-by-Doing (LBD) Sessions where our engineers demo cool and cutting edge technologies. A short time ago, we held a “Flutter Roundtable Discussion” in lieu of the weekly session. We’d like to share our transcript of the topics we chatted about! We hope this will help developers learn more about Flutter.

We’re co-hosting a free, virtual FuN Flutter Night! Sign up and get all details here!

Roundtable speakers: Alvin Fong, Engineering Manager, Bernadine Crisostomo, Agile Software Engineer, Matthew Kwong, Agile Software Engineer, Lalit Agarwal, Software Engineer, Jack Mo, Agile Software Engineer, Uday Patial, Agile Software Engineer

Alvin: Hello everyone! Welcome to our fireside chat / roundtable about Flutter! Today we will be talking about a wide variety of topics across the Flutter landscape. Let’s start off with comparing native development versus Flutter. Anyone can answer this: How is the learning curve of Flutter versus Android?

1/9: Learning Curves

Bernie: I didn’t think the learning curve was too much of an obstacle if you’re already familiar with native mobile development. Making the UI and using the variety of standard Flutter widgets available is pretty straightforward to use. The only obstacle with standard Flutter widgets are the ability to customize their behaviour or appearance. Developing the UI is comparable to React where the Widgets act as the lego blocks to create your screens like React components. The biggest hurdle for me was getting familiar with the architecture such as BLoC and understanding state management– such as when widgets get rebuilt, when overridden functions get triggered, etc.

Matt: Yeah, I think the hardest part was learning about what state management does for you. Widget state management tends to be sloppy and untestable in Flutter; whereas in native development, we know many proven testable architectures. Flutter’s stateful widgets can be completely untestable without plenty of forethought. I think that’s the difficult part of Flutter’s learning curve where the payoff is harder to find.

Bernie: For additional context — In Flutter, there’s no lifecycle methods available to you, compared to Android & iOS. For example, on Android, we have onResume(), onStart(), onStop(), etc. Every UI component in Flutter is either a Stateless or a Stateful Widget. Flutter does not conveniently provide those lifecycle methods. If you need a state to change you would use a stateful widget and then keep setting and maintaining the state. This, however, results in poor unit-testability. Therefore, we leverage the BLoC pattern to help us with state management and widget rebuilding, while increasing testability of our business logic.

Alvin: I find that the lifecycle handling on the native platforms was a lot more thought out. On Android you use overrides such as onResume, onPause, etc., Whereas Flutter skips all those, and offers you build()and willPopScope(). DidChangeAppLifecycleState() feels far less intuitive than the Android lifecycle methods.

Bernie: Another learning curve that we’ve experienced is setting up different platforms settings. It’s required to know how to manage both of the native platforms. On the iOS side, we spent time in XCode to configure podfiles, gain an understanding of provisioning profiles, certificates, etc. On Android, we configured files such as AndroidManifest and build.gradle. Flutter is cross-platform, which means you can’t ignore the intricacies of either platform. You’re going to overcome these knowledge gaps and understand how to configure both platforms, (and iOS just has a lot of configuration and rules that I can’t wrap my head around most of the time).

Alvin: So one of our biggest learnings that we would recommend to any company going into Flutter: It’s definitely worth having experienced iOS and Android developers that can help you navigate both sides of the platform.

Lalit: I think in terms of resources, I feel Flutter lags native development. Suppose you want to do something fancy, such as a custom animated text field, there’s far less libraries available.

2/9: Stateful vs. Stateless Widgets

Alvin: Hopefully we can take some of our components and release them as open source libraries. Let’s talk more about these widgets. In particular, any opinions about using stateful or stateless widgets, and when to use each?

Jack: I find that it depends on what your widget needs to do. If it only needs to maintain a state for itself and is independent of other widgets, then I think being stateful is the way to go. However, if the widget needs to listen to other widgets’ states, or other widgets read information from it, then we implement a stateless widget with a BLoC.

Alvin: I know the BLoC architecture really pushes for stateless widgets. Do you folks find that easier or harder to work with?

Matt: So I think what’s cool about stateless widgets when using BLoC architecture is the fact that you’re guided into creating perfectly reactive code. You’ll be sure that any widget can be built any number of times, always showing the correct information.

One of the biggest pitfalls with stateful widgets is that the framework doesn’t facilitate a widget changing its state while another widget needs to react on it . A lot of more complex UX flows need this behaviour. When you have more than one widget, in parallel, interacting with each other’s state you’ll create a big web of state management in your UI. If any one single component was to rebuild and reset its state (or you needed that to happen), you won’t have a good time managing your source of truth. So I think that’s the benefit of BLoC architecture, and whenever I can make a stateless widget using a BLoC for its state, I know that it will behave in a more predictable way when interacting with other widgets.

Alvin: One of the things I had noticed — a lot of times when we using a navigator to move between screens, we need a buildContext. In a stateless widget you don’t have this kind of context persistence, so how do we handle that?

Matt: I can try to answer that. I believe that it’s actually a big issue — how do you trigger navigation without building the trigger as a reactive part of your initial stateless widget tree? And I don’t think that there’s a good answer for it. You may choose to have your navigation within the scope of your BuildContext (in your stateless widget’s build method). And so, if your navigation happens at the moment a screen is built, that’s great. However, most of the time in a real UX flow, your navigation is a result of some other trigger such as a network call or something asynchronous. Meaning if the navigation was built into your build method, it would need to be a side effect of building the screen’s current state. So usually we will make widgets affected by this issue stateful, so context is readily available in other functions. But this is not ideal because we want to promote using stateless widgets with BLoCs and StreamBuilders, rather than stateful widgets with the setState method.

Alvin: I see in our code, whenever we’re navigating from somewhere with a context, we can use a closure or some sort of callback through a delegate pattern. We then pass it into a stateless widget, then it can make the invocation at the right time. The object with a context then handles the navigation.

Matt: Exactly, so it’s not as straightforward as it is in native development, where at any point of an Android activity or fragment you have access to context. So that does get tricky, for sure.

3/9: Everything is Widgets, Code Organization

Alvin: Ok, another question: how did the everything-is-a-widget mindset change things?

Uday: From the iOS perspective, you have different components such as views or labels dedicated for each purpose. In Flutter, everything is a widget, even a padding or an alignment. It can be tricky to get used to.

Bernie: Yeah, everything is a widget. You’re building a stateless widget with many nested widgets inside and it can get messy with logic that is difficult to follow. We’ve made a folder structure that works for us and helps us with merge conflicts. It also lets us visualize the UI better and see what components are used to build the screen. Dart code can get deeply nested, so we’ve modularized all of our widget components within a screen. For example, in a home-screen package, we have a components package that holds the widgets that the screen is composed of.

Alvin: Cool, sounds like you can separate the component in many files, according to separated concerns. How do you organize this large amount of files?

Bernie: We make as many reusable components as possible and put them in a Common/Shared folder accessible and used throughout the app. What I found works well is each screen being contained in a package that stores everything the screen needs. Different components such as the UI, the BLoCs, etc., are organized in their own subpackages. Everything becomes modularized; it’s super easy to find and navigate while you’re coding.

As an example:

🗂ui_layer 🗂screens

🗂payment_screen 📑payment_screen.dart 🗂ui_components 📑payment_header.dart 📑payment_body.dart 🗂bloc_layer 🗂data_blocs 📑payment_details_validation.dart 🗂ui_blocs 📑toggle_bloc.dart

The same strategy is applied to the data/network layers.

Jack: I think the key is to have widgets that are modular, think of them as components of your application that you can use throughout your application.

Bernie: Yup, we try to make everything as flexible and reusable as possible.

Jack: If you’re coming from a web background, React is very similar in that way.

4/9: Architecture

Alvin: Okay, so going up from the widget level — let’s talk about our high-level architecture that we applied to our apps. I remember when we started using Flutter as a team, we were applying a mvvm approach. Can anyone speak to how we went about that?

Bernie: On TribalPong we used the MVVM approach and it worked pretty well. After using BloC architecture for some time, we prefer it over MVVM as it lets us modularize the codebase even more, and create more reusable reactive components.

BLoC also gives us control to rebuild and isolate certain areas of the screen, instead of rebuilding the whole screen when any state changes. The BLoC maintains the state and outputs the value to the Widget to be consumed. The widget automatically rebuilds itself upon state changes. BLoC also simplifies tests; we don’t have to mock as many things to get our tests to work — Alvin can elaborate on testing between the two architectures.

Alvin: So with MVVM, we create these ViewModels representing everything that you see on a screen at a time. We then pass the ViewModel into our views. Business logic lives in the model layer, our unit tests will look for relevant changes within this ViewModel.

Later, when we moved to a BLoC architecture. For those of you new to Flutter, BLoC stands for Business Logic Component, and its main idea is that you have a series of black boxes, with stream outputs coming from them. These black boxes might have various inputs, or it might have unit tested logic generators inside.

Testing the BLoC is relatively straightforward — treat the BLoC as a black box. The test applies whatever inputs are appropriate, then verifies the stream outputs.

5/9: Continuous Delivery

Alvin: Okay, let’s move on to a different topic! One of our requirements of our current project is that we set up Continuous Delivery pipelines. We first set up pipelines during our TribalScale Internal PingPong tracker project. It took us almost an entire week to set up this kind of automated pipeline.

Our newfound approach turned out to be very reusable, our setup process very repeatable. When we setup our current project we were able to assemble the build pipeline in less than two working days — that’s really amazing. We use Bitrise as a platform to perform our application build on the cloud.

Matt: So Alvin, how did you find creating the Flutter Bitrise pipeline with regards to compiling and building both Android and iOS apps?

Alvin: I think what makes Bitrise stand out from all the other Continuous Integration platforms is that Bitrise is extremely mobile-first. It handles both iOS and Android great and therefore also handles Flutter well.

The setup was very simple, find the correct steps for Flutter in Bitrise’s library and apply them to your pipeline. There’s a few customizations required when changing Android’s build-flavors or iOS build-schemes, but overall it’s not difficult.

I felt the most challenging part was doing the code signing. Android was a breeze, but iOS code signing is as difficult as it is on Xcode. I know our iOS developers listening to this are having a chuckle at that! In fact, we handle the code signing through Xcode. Pair with your local friendly iOS developer to setup the profiles in xcode, then push all these settings to our github repository. Bitrise can then detect that setup and apply it automatically.

Uday: Apple changes their code signing process almost every year, that could be one of the reasons why Flutter hasn’t incorporated the iOS build process into Android Studio.

Alvin: That makes a lot of sense, because Flutter can either make something that works half of the time, or let xCode take care of it.

6/9: Test Automation

Alvin: Let’s talk about how we support our test automation engineering friends in QA. I know Bernie spent a lot of time with one of our QA engineers — could you tell us more?

Bernie: The QA team and our team did a lengthy investigation regarding testing and how we can support Appium, a testing framework, better with automated tests. We chose to use Appium since the Flutter test package is very limited with regards to interacting outside of the app. However, Appium is not super straightforward as we had to come up with some workarounds.

In Flutter, they provide a Semantics widget intended for accessibility and screen-readers. When the Flutter app is converted to Android and iOS, the Semantics widget adds an identifiable label to the DOM.

Semantics allows us to improve accessibility by supporting screen readers. At the same time, QA is able to reference widgets via the labels to execute their automation testing, which is a double win. We’ve found some pain points where a group of Widgets that exist on the same level in the Widget tree were incorrectly generated as one DOM object. We’re still investigating the behaviour of nested Widget trees and how they’re generated to further support and improve automation tests.

7/9: Dart Language

Alvin: That’s great, looking forward to it! Okay, let’s switch gears a little and talk about dart as language — what are your thoughts?

Matt: It’s not very good. Prefixing underscores to privatize access seems insane, in my opinion. Especially because Dart enables us to use named parameters. So if your named constructor parameter must be private (with a leading underscore), you have to do a workaround using the constructor body and manually assigning it to your local private variable since its name is actually different. Dart’s typical constructor syntax prevents declaring a private variable with a named constructor parameter, unlike Kotlin or Swift.

Furthermore, we lack data class types and end up with verbose getters and setters. And using semicolons is outdated. Adding redundant commas to nearly every line in a widget purely for auto-formatting is quite annoying too.

Uday: The coding style is very imperative, lacking lots of functional aspects. I found it very difficult to write functional code in dart, such as .map() or .reduce(). It’s a very imperative programming style as compared to other languages like Swift or Kotlin.

Jack: Yeah, it’s a very object orientated language that allows you to do functional coding, but doesn’t really do it well. It does have .map()but it just doesn’t do it correctly.

Matt: And just this morning, one of our tasks was to use .sort() on a list. We learned that invoking .sort() doesn’t actually return the sorted list, it just mutates the list. We had to copy it into a separate list variable and then invoke .sort() on that object. Coming from Kotlin, where more functional options like sortedBy() exist, it feels very limiting.

Uday: We found out there’s no native lazy initialization in Dart, you have to import a third-party library to do that.

Matt: Yeah, and we apply null checks all the time. They have the question mark syntax for null safety but that doesn’t provide null-safety in a lot of use cases.

Bernie: Yup, sometimes it doesn’t work and you see that big giant red syntax error.

Alvin: Well I see that dart is very young — the latest dart release is 2.7.2, compared to Java, around version 1 million. Kotlin is on 1.3.72. Of course the number doesn’t mean it’s more advanced or not, Dart is a much newer language than the other two.

Does anyone have any other ‘gotchas’ or pain points we can talk about?

This morning I ran into a problem where I was trying to perform a getString() from SharedPrefs. It turned out that if the value hasn’t be written to yet, the method throws an exception. You can use SharedPref.get() and receive either the value as a dynamic type or null. In contrast, the specialized getType() methods can often throw exceptions.

Bernie: Another note on Dart regarding types, you can declare Strings or List types, or you can skip declaring the variable Type, just like Python. There’s also var and val,this allows for a variety of code styles to coexist in one project, and requires discipline and code reviews to keep consistent.

Alvin: The advantages of having these different type styles is that it lets you code in whatever style you’re accustomed to and like. If you’re a coder coming from a Java background can declare using String fooBar, or one from a Kotlin background who prefers

final fooBar or var fooBar, both styles are accepted.

Uday: I consider Dart’s ahead-of-time compilation another one of its advantages. This means Dart code compiles faster than other languages, and easily translates to the native code. That was probably the main reason why Flutter chose Dart to be its language.

8/9: Wrap Up: Nullability, Community, Hot-Reload

Alvin: Awesome… Any last thoughts before we wrap up for the day?

Matt: I know the Flutter team is working on some great things to continue providing this platform with updates for both beginners and more advanced users. It’s nice to see that the community is very passionate and active everyday which makes it a very nice community to be a part of.

Uday: I think Hot-Reload is Flutter’s coolest feature. You can trigger hot-reload and have your changes applied directly and instantly on your phone or emulator. It does work as promised most of the time.

Bernie: Yeah it definitely saves a lot of time. You don’t want to wait 5 minutes for the whole app to build just to see a little quick UI change like changing the padding. Hot Reload definitely comes in handy in those cases! There’s also DevTools that you can launch in the Chrome browser that is super useful! It allows you to see the Widget DOM tree and provides an option to draw blue constraint lines on your device.This enables you to visualize where the widget bounds are and how much space it takes up on the screen. This is extremely helpful when you’re trying to constraint a widget in a specific place and getting everything pixel perfect.

Alvin: That’s essential — Flutter development in Android Studio doesn’t have a quick preview of any kind. You have to run the code on the phone, which only takes about five seconds to refresh.

Okay, so on that note let’s wish dart and Flutter a happy continuation of their development.

Thank you everyone who participated and thank you to the audience for listening in!

9/9: Q&A Session

Audience:

“What is your wish list for upcoming dart features?”

Bernie: I’d love to see a new feature regarding null safety and checking. Right now, the Flutter static analyzer doesn’t raise flags and an object can be assigned null; surprising you with a NoSuchMethodError on a null type during runtime. I wish Dart could tell you “Hey, this could be null, might want to have a null check here” before building. We recommend using the ?. syntax (similar to Kotlin) instead of != null everywhere 😄

Alvin: In many modern languages like Swift and Kotlin, all the types are explicitly nullable or not. That’s something that Dart hasn’t implemented yet.

Uday: Since dart is compiled ahead-of-time. In this kind of scenario, it’s really hard to achieve null-safety. because of trade-offs between performance versus code safety.. Enhancing or enforcing null safety would remove ahead-of-time compilation advantages. Dart is committed to non-null-by-default (NNBD) in a future language release, but there are many syntax details to be ironed out.

Audience:

“How do you feel about Flutter widgets being primarily focused around Material design?”

Matt: Well, in comparison to strictly following Material or Cupertino design, I think it’s quite amazing that you can build one design and it will show up on both screens. So if you’re on the bleeding edge of design and you want to build something that is cross-platform, Flutter is great for you. There are also many Cupertino based widgets you can use if you want that specific iOS look and feel. But many widgets in Flutter’s widget library are going to adapt immediately when built on Android and iOS devices. For example, Flutter’s scrollable views on an Android device will do a bubbly overscroll effect, and on an iOS device, they will perform a bounce overscroll animation for free. That means both platforms can maintain their native feel without needing to add additional logic. When it comes to smaller UI components, it can get trickier because you may want to see completely different things on Android and iOS and you might have to create something more custom for that.

Audience:

“Do you have a wish list of Flutter features?”

Matt: I think what’s interesting is the number of times in the last week or two that we’ve had to dive into the Flutter code to inspect and modify some padding to get the design we want. It seems that some components inside pre-made widgets are not as customizable as they should be. At the end of the day you could have written your own widget from scratch but if Flutter supplies a widget perfect for your situation, it would be nice if all padding in every corner was fully customizable. Without that you run into situations like we did, where we forked the file because we liked everything about the widget except for some unchangeable horizontal padding. This is a smaller issue in the grand scheme of all things Flutter, but I think that supporting these needs could greatly improve the Flutter widget library.

Uday: I have a question, how do you feel about Flutter Web’s future?

Matt: We haven’t explored this area much, but I do know that Flutter Web development is moving. It’s possible to code a website in Flutter and have the same program running on phones with a reactive UI. And there’s also Flutter Desktop, so you can make apps for your Linux, MacOS, or Windows computer — and that’s very cool.

Alvin: Alright, one more question about native versus Flutter:

“For anyone who hasn’t started yet what’s the top consideration that we recommend?”

When we started with Flutter, we went with a simpler architecture than BLoC. BLoC is great once you get a rhythm going, and it brings a lot of positives — but it does have a fair learning curve. If you’re learning BLoC and learning to wrap everything in widgets, it can be too much to take in at one time. I think going with a simpler architecture like MVP or MVVM will make things easier.

Uday: Flutter’s Youtube channel has “Widgets of the Week” that we watched together.

Matt: Yes, Flutter definitely focuses on the beginners and getting everyone into designing their own apps. I don’t think there is much barrier to entry so it’s a great place to experiment without knowing much about programming. Even watching a bunch of those Widget of the Week videos could help you whip up a screen that you’re proud of.

Audience:

“How do we decide between going pure Flutter versus going native iOS or Android?”

Uday: It depends on the scenario. If it’s an app where you are using lots of device features, (e.g.a streaming app with live replays), or you need data overlaid on your video player, Native code would make sense. These things are harder to do in Flutter, and could come with a performance hit. For simpler apps such as ecommerce, we could easily go with Flutter.

Bernie: I could speak to experience as well. When we were implementing and handling native push notifications, we had to use a whole separate Flutter package and had to do a bunch of setup on both native platforms. After all of the configurations, it still wasn’t working perfectly. In those cases, it would be much easier if you did all the setup and handling natively in their respective platforms. I think this goes for anything relying on platform specific features.

We’re co-hosting a free, virtual FuN Flutter Night! Sign up and get all details here!