Sunday, July 15, 2018

Flutter: Showing SnackBar within the Widget that builds a Scaffold

Sometimes you need to implement a simple logic that shows SnackBar. Let's imagine there is a Button that shows a SnackBar.

After pushing the Button you can see the following error:

"Scaffold.of() called with a context that does not contain a Scaffold. No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of(). This usually happens when the context provided is from the same StatefulWidget as that whose build function actually creates the Scaffold widget being sought..."

Scaffold.of() called with a context that does not contain a Scaffold. No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of(). This usually happens when the context provided is from the same StatefulWidget as that whose build function actually creates the Scaffold widget being sought. There are several ways to avoid this problem. The simplest is to use a Builder to get a context that is "under" the Scaffold. For an example of this, please see the documentation for Scaffold.of(): A more efficient solution is to split your build function into several widgets. This introduces a new context from which you can obtain the Scaffold. In this solution, you would have an outer widget that creates the Scaffold populated by instances of your new inner widgets, and then in these inner widgets you would use Scaffold.of(). A less elegant but more expedient solution is assign a GlobalKey to the Scaffold, then use the key.currentState property to obtain the ScaffoldState rather than using the Scaffold.of() function.

It happens because the current context doesn't contain a Scaffold. Even though we see that our build function returns Scaffold. Context starts to look for a Scaffold in the parent and the current build function is dismissed.

Let's implement all suggested techniques to fix the problem (from the screenshot above).

Builder Widget

We start by wrapping the Button with a Builder Widget. It is a platonic Widget that calls a closure to obtain its child widget. To display the SnackBar it uses a context from Builder.

Split a build function

The next suggested approach is to split a build function into several widgets. That's why we move our Button to a StatelessWidget.

The build function has a child context and our SnackBar displays successfully.

Global Key

The last recommended approach is to assign a GlobalKey to the Scaffold. A key that is unique across the entire app. Global keys uniquely identify elements, provide access to other objects that are associated with elements, such as a BuildContext and, for StatefulWidgets, a State. Global keys are expensive, and, in my opinion, it is not a good approach to solve this problem if there are another two approaches that are described above.  But anyway let's have a look at how to implement this. We need to create a Global key instance and assign it to the Scaffold Widget:

After that we will get a state of the Scaffold to show the SnackBar:


Tuesday, February 14, 2017

CircleCI: waiting for Android emulator with Google Play Services to fully boot

The standard circle-android wait-for-boot command works quite well for a vanilla Android image. However, when starting to use google_apis images (those which include Google Play Services, necessary to test e.g. Firebase-enabled apps), the performance of the emulator right after boot is very slow. Lots of services try to start up simultaneously, consuming all available CPU power, which is already not a lot on a free CircleCI container. This results in flaky tests because of timeouts, ANRs of the tested app because of insufficient CPU power, and crashes.

It might seem easy to resolve by saving a snapshot of a fully-booted Android emulator into CircleCI cache, but snapshots are proven to be unstable and crash the device upon load. It's not very convenient on a CI because makes the tests unnecessarily flaky.

One stable way to wait until everything has settled down is to monitor emulator logs with adb logcat command. Once the log spam goes down, it is very likely that most of the work is finished.

Below a small snippet that implements this kind of wait. As a bonus, it keeps logcat entries, which are actually related to the test, separate from that of the other services. Oh and don't forget to unlock the screen, as it might autolock while we wait, and interfere with your tests!

On an Android SDK 19 image the waiting time is about 6 minutes.

Monday, February 13, 2017

Vector Asset Studio and API level below 21

One way to add vector (i.e. slick regardless of the device screen resolution) Material icons to an Android application is to use built-in Vector Asset Studio.

The documentation mentions that if your minSdkVersion in build.gradle is below 21, the build will generate PNG for your supported screen resolutions (which may not be all possible resolutions in the wild, and also may increase the size of your APK). If you don’t want to have PNG you can add vectorDrawables.useSupportLibrary = true, which conveniently adds support for rendering vector graphics inside ImageView (and ImageButton etc).

Note that contrary to a popular myth, it is not necessary to update all layout xml files to use AppCompatImageView: only if in your Java code you inherit your own class from an ImageView (e.g. to extend its functionality), you should change it. But that doesn’t mean that you have to chase after each and every library: if a library isn’t used to display vector graphic (such as using de.hdodenhof:circleimageview to display logged in user’s photo) you don’t have to change it.

However, there’s still a small catch: if you forget to change android:src property name to app:srcCompat in at least one of your layouts, the application will crash (trying to expand the layout) and it may be very compilcated to find the reason. Watch out for new images or buttons that you add later (especially FloatingActionButton) as well.

Sunday, November 20, 2016

ZVV Fare Zones as a Google Maps layer

ZVV (Zürcher Verkehrsverbund, is a public transport network in the Canton of Zürich. The website is rich with all the brochures and various maps. But unfortunately the fare zone maps they share in PDF are not easy to view on mobile, and even harder when it comes to deciding whether a particular bus stop belongs to one zone or another.

So if you are looking for a Google Maps overlay that will precisely show you all the ZVV Tarifzonen, seek no more: it's right here!

If you want to make your own, here's a small SVG to KML converter that will download the most recent ZVV zone map and convert it into KML, which can then be imported to My Maps: GitHub Gist.

It is very simple, so it does not render zone numbers. Perhaps that's the reason why ZVV refused to provide KML when I offered them to add this feature?

Wednesday, June 1, 2016

Arduino Wire.h and i2c

So I was implementing an i2c slave on the Rover's Arduinoes; the one that could be read from and written to. All Arduinoes are operating in slave mode, the bus master is a Raspberry Pi board.

For this apparently you have to install both onReceive and onRequest handlers; the "write" operation is simply handled by onReceive by receiving the register and immediately after that a new value; the "read" is a bit more complicated, as onReceive receives the register from the wire, and then onRequest that follows needs to send the data.

All that works quite fine until the speed at which Raspberry Pi queries the Arduinoes is skyrocketing to where Wire library apparently gets confused. In practice it is seen as if an Arduino gets stuck sending -1 to whatever request it receives.

It was bothering me for a while until I decided to figure it out yesterday. Since it's hard to debug an onReceive/onRequest handler via a Serial console (merely because doing Serial operations in an interrupt handler is impossible), I had to look through the Arduino libraries code trying to guess what's going on.

After a while I found a set of instructions that looked suspicious. Apparently Wire library is not accepting any more data unless the buffer received from the previous i2c operation is completely read from!

So the solution looked simple to me; on onReceive, just read from the buffer until it's empty, to make sure it's called the next time data arrives. I'm not sure what exactly confuses Arduino so that it receives more data than Raspberry Pi sends, but it's good to clear the buffer as a safety measure in case someone writes more data by accident.

P.S. From the Wire library source code I also learned that the integer argument passed to onReceive is the same thing returned by Wire.available() before any reads are done. That's because they are computed in the same way, and onReceive, which is called from an interrupt handler, is non-interruptible, as any other Arduino interrupt handler.