Android Archive - CraftCoders.app https://craftcoders.app/category/android/ Jira and Confluence apps Wed, 14 Aug 2024 11:46:37 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.3 https://craftcoders.app/wp-content/uploads/2020/02/cropped-craftcoders-blue-logo-1-32x32.png Android Archive - CraftCoders.app https://craftcoders.app/category/android/ 32 32 OpenCV & Tesseract: Android Computer Vision for Dummies https://craftcoders.app/opencv-tesseract-android-computer-vision-for-dummies/ https://craftcoders.app/opencv-tesseract-android-computer-vision-for-dummies/#respond Mon, 06 Aug 2018 13:52:38 +0000 https://craftcoders.app/?p=461 Read More]]> Introduction

I got to know OpenCV as I investigated into computer vision solutions for a little startup called robodev GmbH (see here). There I developed possible ways for real-time object recognition and computation of angle and position of the recognized object. I fell in love with C++ and its possibilities to realize high-performance applications. Awhile I started digging further into Android development in Java. Due to my experience as a mobile (games) producer at Goodgame Studios, I’m driven by the possibilities and potential of mobile solutions, so plenty of awesome app-ideas get born in my brain. Now is the time I make my ideas come to life.

Come to life

I wrote my first blog post about how I got into programming (see here) and how you can do that too, even when you’re not from the IT-field. In this post, we will play around with OpenCV in Android. Since I didn’t have a small use-case in mind for the vast feature-list of OpenCV I’ll keep that part simple.

For my first app idea, reading business cards could be a neat side-feature, and as I heard that OpenCV and Tesseract are a powerful team – process pictures with OpenCV and read texts with Tesseract OCR – I saw an excellent opportunity to put both into a thin app.
In this blog post, I will show you how to write that app all by yourself, step by step. You will learn to use C++-code, OpenCV and Tesseract OCR on Android.

What is OpenCV?

OpenCV is an open-source computer vision library. It has a mighty feature-set, starting with simple image processing over object-, face- and feature recognition, 3d-calibration, and visualization up to machine learning and more. There is a big community with scientists, professionals, and hobbyists maintaining a comprehensive documentation – helpful for everyone, including beginners (see here). I was more than happy realizing that it supports Android development, next to C++, C, Python, Scala, Java. Unfortunately, the inbuilt OCR is not known to be very mighty, therefore I decided to combine the strengths of OpenCV’s image processing with another library, called Tesseract (as a Marvel Fan I really like that name).

Loki

What is Tesseract OCR?

Tesseract is a free software, command line program for Linux, Windows, and next to the support of 100+ different languages, it was considered the most accurate open-source OCR available in 2006. In 2011 a brave man called Robert Theis released a fork version called “tess-two” of Tesseracts OCR Android tools, maybe not knowing that just 2 years later no one would work on Tesseract Android Tools anymore (the last commit was made 2013). Thanks to Robert Theis we can enjoy the opensource library up till today, updated, with new features and easy to use. So let’s get to work and get the setup of your Android Studio done:

OpenCV Part

Setup Android NDK

To be able to use OpenCV in its pure form we first should setup Android NDK properly, and yes… we’re gonna write C++-Code in our App πŸ˜‰ AAAHHH

After creating a new Empty Activity Project use the Android Studio SDK Manager (Tools -> SDK Manager -> SDK Tools) for installing the necessary Tools: NDK, CMake, and LLDB. Mind the location of your SDK though – you’ll need that for the next step. After applying your changes a long download of around 700MB will start – don’t worry, that is as intended.
sdktools_setup

Next time you wake up from sweet dreams maybe the download is over and you can keep working. Now you need to add the location of your Android NDK. Write it into Files -> Project Structure -> SDK Location -> Android NDK location. Yes, you just saw the needed path: it is the SDK location + /ndk-bundle.

Android NDK Location

There you go. Your first step to glory is over. The next stuff is going to be a bit more tricky. OpenCV – here we come!

Setup OpenCV

To setup OpenCV, the first step is to download the latest release of the library from their page: OpenCV Releases (at this point it’s 3.4.2). Unpack the zip, preferably into the Android SDK folder (so you have it all in one place) and import that stuff, as a module, into your project: File -> New -> Import Module. Navigate in the next dialogue that pops up to the java-folder of the just unpacked module, i.e. mine is /home/soeren/Downloads/OpenCV-android-sdk/sdk/java (on Linux). If you’re correct the name of the library will show up and it looks like the following (ignore “Project already….” in that pic):

import module window

Hit “Next” and “OK” as long as they ask you stuff, keep going with the default checkmarks until you finally finish. Now, add the module as a dependency to your app. At File -> Project Structure -> app -> Dependencies -> + -> Module dependency you will find OpenCV shown in a pop-up.

add module dependency

Next step is to correct the build.gradle-file of the OpenCV Library (you will find it right under your app build.gradle-file). Align the fields compileSdkVersion, minSdkVersion, and targetSdkVersion, so they are matching with the build.gradle-file (app). Mine looks like this:
build.gradle (openCVLib…) for initial setup

Now you create a directory in your project src/main/jni. In there you create a file called Android.mk and another called Application.mk – to tell the NDK tool to build native code.

where to put the mk-files

Write the following code into the Android.mk-file:
Android.mk for NDK and OpenCV setup

Write your Android NDK path into NDK_MODULE_PATH and replace the OPENCV_ROOT according to yours. Later we will add the LOCAL_SRC_FILES name, but for now, we can leave it empty. Then modify the Application.mk-file:
Application.mk for NDK setup

Now you can tell your build.gradle-file (app) to compile C++-code to .so-files into the jniLibs folder. Do that by writing this…
build.gradle (app) File for OpenCV setup
…into your build.gradle (app) file, by overwriting the default buildTypes-part. Remember to change the ndkDir value to your own. Don’t hesitate to process the gradle-sync, but if you try to build your app now you will get a wonderful error. Something like Process 'command 'PATHTONDK/ndk-build'' finished with non-zero exit value 2". This cryptic error messages did cost me quite some patience, but don’t worry. This is still as expected because we didn’t give in any LOCAL_SRC_FILES. That’s what we’re going to do now. For the sake of convenience, we’re going to use javah as an external tool. For doing so, navigate to File -> Settings -> Tools -> External Tools -> +. Add the javah tool like this:

javah setup

Fill the parameter the following
* “Program”: the path to your javah installation (with Linux you can find out the path typing which javah into a terminal)
* “Arguments”: -v -jni -d $ModuleFileDir$/src/main/jni $FileClass$
* “Working directory”: $SourcepathEntry$

After successfully setting javah up, write a little test function into your MainActivity:

public static native int test();

Use the javah-tool via right-click on test()-> Android Tools -> javah and you generate a corresponding header file and can find it in your jni-folder. Copy the name of that file into your Android.mk-file to LOCAL_SRC_FILES, mine is called com_example_android_MYAPPLICATION_MainActivity.h, yours will be something close to that, I guess.
Now, run and pray that you didn’t make any path- or copy-paste-mistake. If something is not working and you get a cryptic error-message – check all the paths again, and when that doesn’t help, start over again – been there… done that… can happen to everyone! Just don’t give up. πŸ™‚
If it builds successfully and a “Hello World!” is smiling from your Device-Screen up to you: YEAH!!! You’ve done great so far. The actual fun starts now!

Setup the Camera

To use the camera we’re going to implement some code. Initially we implement our MainActivity and its XML-structure. This is going to be a small button on an empty screen, nothing more. But there is more to come! I prepared a gist for you, where you can simply copy/paste the code into the corresponding files: MainActivity for initial setup (don’t forget to change the package name)

Our next step is to create a new empty Activity for working with the camera, let’s call it CameraActivity. Here we will call the camera and have fun with OpenCV and tess-two later, but for now, we will only call the camera. Copy the code from the following gist into the corresponding files: CameraActivity initial setup.

As you can see, we really should take care of state changes, due to the fact that the camera is pretty demanding to run constantly. New to you will be the onCameraViewStarted, onCameraViewStopped and onCameraFrame-methods. Here you can directly interact with the camera input.
In our MainActivity we will call the CameraActivity, by replacing the // do stuff here with the following code:

Intent intent = new Intent(MainActivity.this, CameraActivity.class);
startActivity(intent);

Well done! You should be able to run the app now. And if you press the button… wait! OpenCV Error Msg

What is this? Ahhh!! We forgot the permissions, can it be it’s the permissions?

Get the permissions for OpenCV

So for getting the permissions, we will check and if necessary ask for them in the MainActivity, when the user presses the button. Modify the MainActivity with all the uncommented code at their proper position (as shown with the comments): Permissions in MainActivity

The last step to get the permissions is to touch the AndroidManifest.xml: Manifest for camera permissions

Okay, let’s see if this was the mistake and run the app…Asking for permissions seems to be working but…Package not found

Oh come on! Nooooo!! Press “No”! We don’t need that ominous “OpenCV Manager”! We already have the whole library inside our app.

oh come one!

Ok ok…calm down. We can easily correct the rude OpenCV behavior:

Ignore OpenCVManager

So one of the most useless features of OpenCV is, that it checks if the device it’s running on has the OpenCV library installed, and if not, it won’t work until the OpenCV Manager App is installed. Since we put the whole library into the actual app, we don’t need that anymore, and yet, due to the fact that the library isn’t loaded completely when it checks it causes this message. With the following gist, we override the onPackageInstall-method of the BaseLoaderCallback class to prevent the app to trigger the installation. Add the following uncommented code into your CameraActivity right into the BaseLoaderCallback class:
BaseLoaderCallback-class override the onPackageInstall-method

Now run the app again. If you didn’t mix up some code it should finally look like this:

First cam shot

Good job! But no time to celebrate: Somehow the camera orientation and size makes you feel a bit dizzy, right? We are getting our hands dirty and change something right inside the OpenCV library to fix this one. Find the class CameraBridgeViewBase here:

CameraBridgeViewBase Location

Look for the method deliverAndDrawFrame and replace it with the following code: Adjusting rotation and scale of the camera

Yeah! How wonderfully straight it looks like this, right? It somehow feels like the first time we see stuff through our camera. If your camera view seems a bit too small or big, you can simply change the mScale value accordingly. If you made it till here, congratulations! Figuring out the whole procedure till here cost me around 2 days of my life! I’m glad to make sacrifices for you, though πŸ˜‰ The next part is going to be much easier!

Use the OpenCV library for image processing

Let’s start putting some functionality into our app and add some more buttons to our activity_camera.xml and corresponding listeners to our CameraActivity:
Adding function buttons, listeners and call OpenCvMaker methods

Don’t mind the errors yet, we will get to those now. Create a Java class with the name OpenCvMaker.java and add the following code (and again, remember to change the package name):
Create an OpenCV manager class

Now delete the previously generated header file, from as you set up the native test() method with the javah tool.

Generated header file

Navigate to your OpenCvMaker class, use the javah tool on one of the native methods (i.e. makeGray(long......)) with right click -> Android Tools -> javah. A new header file will get generated. Go to this file and make the following changes:
Add methods to the header

Then, inside of the jni-folder, create a .cpp-file (without header) and call it the same as your header and add the following code to it:
Implementation of OpenCV methods in C++

And last but not least, copy the name of this .cpp-file (including the “.cpp“!!) and set it as your LOCAL_SRC_FILES inside your Android.mk-file.

Run your app and…tadaaa! Now you can play around with four of the most common functions of OpenCV. Well done!

celebrations

You just learned how to use C++-code AND OpenCV functionality inside your Android app. If this is all you wanted to learn, give yourself a break and lean back. Give your beloved ones a hug and tell them, you did it! Also, give yourself a break when this took you too long and your computer is smoking from all the building processes. In times like these, during the hot summer, I prefer to put my laptop into the fridge to give it a little chill πŸ™‚
my computer in the fridge

Tess-Two Part

Now that we had a wonderful bath in a jacuzzi full of self-esteem, we can go back to work. There is one little part missing: The OCR! Don’t worry, I will make it quick.
First, add the following dependency to your build.gradle (app)-file and trigger the gradle-sync:

implementation 'com.rmtheis:tess-two:9.0.0'

Tess-two is now available and ready to use. Add the HUD components to activity_camera.xml and some functionality to CameraActivity.java with the following modifications:
Read-button and textview for CameraActivity

Create a new Java class with the name MyTessOCR.java and put the following code into it (don’t forget to change the package!):
Implementing the tess-two class

Let’s use it in the CameraActivity, and make some changes in the CameraActivity. Since computation of the OCR could be process-demanding, we will call it in an extra thread. Also, you will be asked to import the android.graphics.Matrix, continue and confirm. This is only for rotating the input signal of the camera according to the preview, that we’ve rotated inside the OpenCV library already. Do the following: Call MyTessOCR class from CameraActivity

Tesseract, and therefore tess-two, needs traineddata to know how and what to recognize. Since we are using tess-two, which is a fork of an older Tesseract version, we cannot just use any traineddata that is available in the tesseract git repository. Download the following traineddata and you will be fine: Download eng.traineddata. Create an asset-folder via right-click on Java -> New -> Folder -> Asset Folder, then create a directory called tessdata and finally copy the downloaded eng.traineddata-file into that directory. When you’re done it should like this:

traineddate location

Next, you need to make sure that this file is also put onto the device. Therefore we need to add a little method to our CameraActivity and call it in the onCreate method: Add preprateTessData-method to CameraActivity

Alright! The last thing to do is to get the permissions for read and write access on the external storage since we are doing that with our previously added method. Go and add the following checks and constants to the MainActivity and additional permissions to the AndroidManifest.xml:
Ask for external storage read and write permissions

Okay! Breath in. Breath out. You did it! Build (can take a while), run and play πŸ™‚ Hold your phone in front of the text below and press READ IT! in your app.

Congratulations

Find my original Git repo of the app here

]]>
https://craftcoders.app/opencv-tesseract-android-computer-vision-for-dummies/feed/ 0
My date with Dart and Flutter https://craftcoders.app/my-date-with-dart-and-flutter/ https://craftcoders.app/my-date-with-dart-and-flutter/#respond Mon, 09 Jul 2018 13:54:34 +0000 https://billigeplaetze.com/?p=334 Read More]]> Motivation

…Hey, guys! It’s time for another blog post from me. This time I want to write about my experiences with Flutter and Dart. Every software developer is regularly confronted with new concepts, frameworks or languages, so am I. I learned to program with Android 2.0. Yes, I’m getting old… Meanwhile, I am not so active in the Android world anymore. But since we code Android apps on hackathons every now and then, I don’t get out of practice. Nevertheless, I am a little spoiled by the development of UWP apps. Also, the time on hackathons is very limited, which is why one does not like to deal with boilerplate code. Let’s be honest… Android (Java) is full of it! Therefore, alternatives are needed. Okay, so it would be obvious to try Xamarin now, but where would be the fun? I have no plans this weekend and Google seems to have created a real alternative with Flutter and Dart! Sounds like a hot date ? I’ve never worked with Dart or Flutter before, but at some point, you have to start. So this post will be about my experiences and not a tutorial.

So what are the advantages of Flutter?

Google promises not only that you can develop apps very fast, but also that they run on iOS like Android. In addition, Dart and Flutter are focused on developing reactive apps. I’ve never written apps in a reactive manner before, but at least the theoretical foundations were covered at our university. *Yes, Dannymausi I’m talking about the hipster teacher πŸ˜‰ *

And what do I want to program?

I’ve been thinking for a long time what I can achieve in less than two days (hitting the gym and watching Netflix is still important!). I don’t know the language or the framework…. But yolo , after all, Google promises that you are significantly faster in developing. So here’s the idea: We’ve finished the first rotation of our blog posts. We came up with the idea that once every member of us has written a blog post we select the best and the winner gets a little something. Basically, this means we have to make a voting every 5 to 6 weeks. There is one catch: Since Danny is from Russia we have to very be very cautious about our voting system. We had so many different voting systems… I can’t even count them! We even voted about how to vote… Time to time we vote with two or three voting shares. Sometimes we have several voting rounds. Since we always discuss and try to optimize our voting systems, it’s time for an App. The app should allow you to vote for one of the five possible authors (including yourself). After you have voted, the application navigates to a second page where you can see the election results. You can not vote again. However, a new election can be started, this must be propagated to all clients.

Getting Started

So now I have two days to try out a new language and a new framework….. What the hell am I doing to myself ?? So that it won’t be too unpleasant, I decided to chill on YouTube first. Fortunately, the Google IO 18 wasn’t so long ago and there are interesting talks about Flutter. Here are the ones I looked at:

  1. Build reactive mobile apps with Flutter
  2. Total mobile development made fun with Flutter and Firebase

So what should you take out of these videos? On the one hand Flutter is about widgets – everything is a widget. A screen is a widget tree. One distinguishes between Stateless and Statefull widgets. But we will use the BLOC pattern from “Build reactive mobile apps with Flutter” for our app.
On the other hand, Firebase seems to offer itself perfectly as a backend. As this blog post is not a tutorial, I recommend to watch the videos. Our task is to create a Firebase database. And to create a client according to the BLOC pattern.

Implementation

The Backend alias Firebase

So this is the easy Part. Since I have already written such a long motivation, how about a gif?

How to create the Backend πŸ˜‰

The Voting Screen

To keep the code manageable I decided to write all widgets in the main.dart file. In a real project you would surely divide them into different files but for experimenting, I think it’s better to keep everything at one place. Since time is limited, I thought we’d keep the layout simple. Here is my design:

Lets Check:

  • We have a ListView
  • We have an AppBar with Title

That’s what I call minimalistic πŸ™‚
After I haven’t committed to any tutorial here, I don’t want to go into the exact structure of the code. Much more interesting is how we build our UI based on the authors in Firebase. First, we have to connect Firebase to our app. Just follow the instructions on Firebase or watch this video.

From Firebase to Voting

But what is the right place to read from the database? If we follow Google’s BLOC patterns, we need a stream. Using this stream, information from our database will flow straight into our UI. So we don’t have to hold a state. But what if we need information from the database? So not the information from the database flows into the UI but rather the UI pulls information from the database? To solve this elegantly, we would need a short-term memory. Therefore we will let the stream from the Firebase database flow into a so-called BehaviorSubject. This BehaviorSubject can always return the last element seen on request. So it holds our state.

Finally, we need a so-called StreamBuilder in our view. The StreamBuilder builds itself on the latest snapshot interaction with the specified stream an whose build strategy is given by a builder. So lets look at the code:

    class Bloc {
            final BehaviorSubject<QuerySnapshot> _firebaseSubject =
                    BehaviorSubject<QuerySnapshot>(seedValue: null);

            Bloc() {
                _firebaseSubject.addStream(Firestore.instance.collection("AuthorVotes").snapshots());
            }

            Stream<QuerySnapshot> get firebaseStream => _firebaseStream.stream;
    }

Here we connect the BlocClass with the Firebase Database alias Firestore

    class AuthorGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final bloc = BlocProvider.of(context);
    return new StreamBuilder(
      stream: bloc.firebaseStream,
      builder: (context, snapshot) {
        if (snapshot != null && !snapshot.hasData) return const Text('Loading...');
        return ListView.builder(
          itemCount: snapshot.data.documents.length,
          itemBuilder: (context, index) => 
            _buildItem(context, snapshot.data.documents[index]), 
        );
      },
    );
  }

Here we build a widget based on the stream comming from our blocclass
But wait?! Where comes the BlocClass from? You’re right, the BlocClass itself is also a kind of state we need to have manage. Although the UI of this app is manageable and I could have passed the bloc object through the tree as a parameter, there are better alternatives. With the help of an inheriting widget we are able to reach the object from the entire subtree.

From Voting to Firebase

But How do we get updates to the Firebase? Actually, it’s pretty easy. We just have to let a stream flow in the opposite direction. For this purpose we create a stream controller in our BlocClass. The StreamController controls our stream. It allows to feed events into the stream via a sink as well as to consume the stream by calling the stream methode. All we have to do is: (1) A Click on the list submits an event to the stream. (2) Consume this stream and start a Firebase transaction at the appearance of a event.

class Bloc {
  final StreamController<Vote> _voteController = StreamController<Vote>();

  Bloc() {
    _voteController.stream.listen((data) => commitToFireBase(data));
  }

  Sink<Vote> get voteSink => _voteController.sink;

  void commitToFireBase(Vote vote) {
    Firestore.instance.runTransaction((transaction) async {
      DocumentSnapshot freshSnap = await transaction.get(vote.votedAuthor.reference);
      await transaction.update(freshSnap.reference, {'Votes': freshSnap['Votes'] + 1});
    });
  }
}

This is all we need in our BlocClass. As you can see the voteSink is the place to commit new Events to our Stream. In the Constructor of this Class, we say that the stream is consumed by a function, which is also defined within the Blocclass. In this function, we start a transaction to safely increment the votes for the author by one. Notice how the actual Author is provided by the vote event itself.

Widget _buildItem(
    BuildContext context, DocumentSnapshot document, AuthorBloc authorBloc) {
  return new AuthorSquare(
    author: document['Author'],
    imageUrl: document['Image'],
    onTab: () {
      bloc.voteSink.add(Vote(document));
    },
  );
}

As you can see we just add a new vote event. There is no logic in the UI – all is done by in the BlocClass.

Navigation

I know the blog post is getting long and I said it won’t be a tutorial. But there’s another detail I want to work out. This has given me the most stuggle and I’m sure there are more elegant ways to solve it. Nevertheless, I would like to share and discuss my solution with you. The challenge I encountered was navigation. Yeah, Flutter’s got a pretty well-designed system for navigation. But what I want is more than the standard requirements. So here a some requirements which I had set for myself:

  1. As a voter, after making my choice, I don’t want to be able to re-vote.
  2. As a voter, when I open the application I want to see the election results directly, if I have already voted.
  3. As a voter, I want to be brought directly to the polls from the election results if there is a new election.
  4. As a noob in reactive programming, I don’t want the navigation logic to be located in the view.

Well, as you can see, I didn’t make my life easy. One difficulty was to get the BuilderContext from the widget into the blocClass. However, you need the BuilderContext to create a new route. In Flutter Navigation is based on so called “Routes”. A Route is a mapping from a name to a widget. The Navigator managed this routes by adding or removing them from the navigation stack. So first we needed to get the BuildContext to the blocClass. For this we can use a BehavoirSubject again. When we create a widget, we pass the BuildContext as an event. Since a BehavoirSubject always remembers the last element seen, we are always able to navigate. Furthermore we can pass the widget we wish to navigate to into another stream. If we combine both streams, we have everything we need to navigate.

It is important that we do not use BehaviorSubject for the widget stream. We can’t remember the last widget we saw. Otherwise we would end up in an endless navigation. Therefore we use a StreamController for the wigetstream. The BuildContextStream and the WidgetStream are fed into a zipper. The zipper then packs the elements from the two streams into a tuple and the cool thing is: Since we use a BehaviorSubject for the BuildContext, we always get a context here. But since the widget comes via aStreamController, a tuple is only generated if we actually have passed a widget somewhere in our code into the stream.

class Bloc {
  final StreamController<Widget> _widgetController = StreamController<Widget>();

  final BehaviorSubject<BuildContext> _contextStream =
      BehaviorSubject<BuildContext>(seedValue: null);

  AuthorBloc() {
    Observable
        .zip2(_widgetController.stream, _contextStream.stream,
            (a, b) => new Tuple2<Widget, BuildContext>(a, b))
        .listen((data) => _navigate(data));
  }

  Sink<BuildContext> get contextSink => _contextStream.sink;
  Sink<Widget> get wigetSink => _widgetController.sink;

  void _navigate(Tuple2<Widget, BuildContext> tuple) {
    Navigator.pushReplacement(tuple.item2,
        new MaterialPageRoute(builder: (BuildContext context) => tuple.item1));
  }
}

If you look at the picture from above, the code is quite straightforward. If you want to navigate simply pass the widget to navigate to. For example, when we realize that the voting results have been thrown away:

void _checkForEmptyVotes(QuerySnapshot data) {
  int votes =
      data.documents.map((snap) => snap.data['Votes']).fold(0, (a, b) => a + b);

  if (votes <= 0) wigetSink.add(new MyHomePage());
}

You’re wondering where this code is coming from again? Easy, actually. Since we already have a stream from the database, we just have to let it flow into this function. With each database update, the system checks whether navigation is required.
The other requirements are now also easy to implement. We simply write in the local settings whether we have already selected. If so, we pass the corresponding widget the stream and already we navigate.

The Result

Time for a gif!

  1. You can only vote once ?
  2. If someone votes, all devices are getting the update ?
  3. When a new election is started, all devices navigate to the election.?

Reflection

That’s been a great two days! Even though I had to fight with a lot of initial pain, I am glad to have chosen this topic. By initial pain, I mean trivial things like spending a lot of time for googling the basics of Dart, being depressed from simple compile errors or even setting up Flutter and Dart on my Computer. Despite still pending refactoring I am also relatively satisfied with the code. Even if I will hate myself for this code in a month πŸ™‚ The concept behind Flutter is simply great so far! Personally, I find the development experience much more satisfying than vanilla Android with Java/Kotlin. I also liked Dart as a programming language very much. I liked the tooling around Flutter, too. By the way, I used Visual Studio code.
Well I will surely change some code and build some model classes. Also I didn’t care about the design yet. But there seems to be still a lot to discover here. After all, Flutter is there to build beautiful apps! I am not yet satisfied with the navigation either. For example, I don’t like my solution, which is waiting for an update of Firebase (also for the device that requested a new voting). But you will certainly read more of me and Flutter ❤

Feel free to check out the GitHub repo

]]>
https://craftcoders.app/my-date-with-dart-and-flutter/feed/ 0
Xamarin: Tabbed Page Navigation https://craftcoders.app/xamarin-tabbed-page-navigation/ https://craftcoders.app/xamarin-tabbed-page-navigation/#respond Mon, 25 Jun 2018 08:00:56 +0000 https://billigeplaetze.com/?p=92 Read More]]> If you have a background in native mobile development and for some reason had to switch to Xamarin, you are probably familiar with that “oh yeah, it doesn’t work this way here” feeling. Having to find new solutions for problems you haven’t even considered to be problems before is what I would call business as usual in Xamarin. Call me a weirdo, but that is also what made me enjoy learning the technology so much.

The project I had to dive into cross-platform development with, was a big adventure on its own – a very ambitious idea, a rather vague picture of the expected result (and therefore constantly changing requirements), a critical lack of time and the total of 3 developers, all rather new to the technology. Time pressure, general cluelessness about Xamarin and no expert looking over our shoulder turned into hours of online research, in a desperate hope that someone has already encountered the exact same issue. I know that, as any other developer, you have been there yourself, and I don’t have to tell you about that inexplicable relief of finding what you were looking for. Just as much as I don’t have to elaborate on the frustration of trying to fix the problem that you’ve created, trying to fix the problem that you’ve created, trying to fix the problem…

One of the issues, which has nearly caused such infinite loop of research, was connected to building up a pretty common navigation concept. Here is what we were trying to achieve:

After logging in, a user should see their dashboard. From there they can navigate by switching between individual tabs. Those are always present on the bottom of the screen, “framing” any active content. Each tab has it’s own navigation tree, meaning one can go back and forth between pages inside any tab. If a user decides to switch between tabs, the app remembers current position in all the trees. This way one can always pick up from where they left the tab, no matter what has happened in the meantime.

This navigation concept has been around for a while, and you could probably name a couple of apps using it off the top of your head. However, just like I have mentioned earlier, things in Xamarin are not necessarily what, where and how you are used to. It’s not rocket science, but if you are new to the platform, this little tab page navigation tutorial could hopefully save you some headache. Let’s dive right into it.

Xamarin Navigation Basics

1. Defining MainPage
The root page of your application is defined in App.xaml.cs, which is a part of the shared code. Here you can tell your app, which page to display on start.

public App()
{
    InitializeComponent();
    MainPage = new MyMainPage();
}   

2. Hierarchical navigation
Hierarchical navigation is the most basic way to move between pages in Xamarin. You can think of it as a stack of cards in solitaire. The first card in the stack is your root page. You can put new pages (or cards) on top of it in any order you like, as long as you comply with a couple of predefined rules. In a solitaire, these rules would tell you, say, to only put lower ranked cards on top of higher ranked ones. In a Xamarin app, the rules define legitimate navigation paths. They are nested in the code and influence the way a user can interact with your app.

So how does it look from a less abstract, more code-oriented perspective? Your navigation tree is represented by a NavigationPage. You can pass the root page of the tree as a constructor argument, like this:

var myNavigationPage = new NavigationPage(new MyPage());

Now that you have your base card on the table, you can add some more on top of it, by performing a push operation. In this example, you push a new page on the navigation stack after a button is clicked:

private async void Handle_MyButtonClicked(object sender, EventArgs e)
{
    await Navigation.PushAsync(new MyOtherPage());
}

If you want the top page of the stack to disappear and show the page below, do so by calling the pop method of Navigation property:

private async void Handle_DismissButtonClicked(object sender, EventArgs e)
{
    await Navigation.PopAsync();
}

Now that you can manipulate your stack, it’s time to check out, how tabbed pages work in Xamarin.

Tabbed page navigation

Step 1. Creating a Tabbed Page

public partial class MyTabbedPage : TabbedPage
{
    public MyTabbedPage()
    {
        // Your constructor code here
    }
}

All we do here is inherit from a TabbedPage class, which is provided by Xamarin. It is important to remember, that tabs will not render the exact same way on different platforms. On iOS, they will show up at the bottom of the screen, beneath the actual page content. Android, on the contrary, displays tabs on the top of the page. There are many more differences in tab behavior across the two platforms, the most important ones covered here.

Note: For our scenario, we had to go for a unified look (icon tabs on the bottom) for both, iOS and Android. If this is what you are going for as well, check out BottomTabbedPage control by Naxam.

Step 2. Making TabbedPage to your root
All you are doing here is telling your App to display the tabbed page you’ve created in the previous step on start. Use MainPage property to do so.

public App()
{
    InitializeComponent();
    MainPage = new MyTabbedPage();
}   

Step 3. Filling the tabs
How many tabs is your app going to have and what are they going to be? When you’ve decided on the navigation concept and created all the necessary pages, all you need to do is pass them to the tabbed page.

Let’s go back to our solitaire metaphor for this one. Imagine, that your game has a row of three cards in the beginning. Maybe, two of those are the basis for stacks a player needs to build, and the third one is just a single card, pointing at a trump. How do you create an analogy of this constellation within a tabbed page?

The game field you lay out your cards on is your tabbed page. It should display three cards in one row so you will need the total of three tabs. One of them is very simple since it only has one card in it. Nothing goes on top of that card, and the player does not have the power to alter it. For our app, it means that no navigation takes place inside the tab. This behavior is going to be represented by a ContentPage.

The other two cards have a more complex role. They will become stack bases. Cards are going to be put on top of them, and, maybe, taken back off. For our pages, this means that they are going to become roots of their own navigation trees, which users will interact with. By pressing buttons or swiping, they will travel from one page to another, pushing them on or popping them off the stack. To enable this, we will need a NavigationPage class to wrap around our root page. It will allow us to use the push and pop methods and let our users travel up and down the stack.

An important notice here: you should not wrap TabbedPage itself into a NavigationPage. iOS does not support this, and you are going to run into troubles trying to do so. Your concept should base on populating a TabbedPage with ContentPages and NavigationPages only.

Finally, you can add content and navigation pages to the TabbedPage using its Children property:

public Tabbs()
{
    // Your constructor code here
    Children.Add(new NavigationPage(new MyPage());
    Children.Add(new NavigationPage(new MyOtherPage());
    Children.Add(new MyContentPage());
}

Step 4. Manipulating the stacks
NavigationPages give you a couple of methods to work with the stack. We are only going to look closely at the two most basic ones today: PushAsync() and PopAsync(). These methods can be called on the Navigation property of a Page and allow you to put a new card on top of your stack or remove the top card.

Any class, which derives from Page provides you with a Navigation property. Sometimes, however, it might be useful to manipulate the navigation tree from outside the pages themselves. In order to expose the tree to other classes in the app, you might consider declaring a public variable in your App class:

public static INavigation Nav { get; set; }
public App()
{
    InitializeComponent();
    //...
    var myNavigationPage = new NavigationPage(new MyPage());
    Nav = myNavigationPage.Navigation;
}

After you took care of this, you can access the navigation tree from anywhere inside of your app:

private async void Handle_SomeNavigationButtonClicked(object sender, EventArgs e)
{
    await App.Nav.PushAsync(new MyOtherPage());
}

While this step can be useful regardless of the navigation concept of your application, it was absolutely crucial for our multiple-navigation-stacks solution, because we needed to know which branch of our navigation tree we are on at all times.

Step 5. Knowing where you are
How do you let the App know which tab is currently active so that the pages get pushed to the right stack? One way to achieve this would be to let TabbedPage take care of changes by overriding its OnCurrentPageChanged() method:

public partial class MyTabbedPage : TabbedPage
{
    public MyTabbedPage()
    {
        // Your constructor here 
    }

    protected override void OnCurrentPageChanged()
    {
        base.OnCurrentPageChanged();
        App.Nav = CurrentPage.Navigation;
    }
}

Now, whenever you are calling the Navigation property using App class, the active stack is going to be chosen.

Extras

The basic idea of navigation is simple. You have an overall context, your App class, which has the information about your current location and provides a mechanism to interact with it. You can use this mechanism from anywhere inside the application to go forward and backward in the navigation stack. Classes requesting such manipulations do not have any knowledge about the selected tab or current position in its navigation stack. All they need to do is ask the App to perform a specific operation on the right stack.

Depending on your project, you might need some extra functionalities in connection to your TabbedPage. Here are two optional steps we had to use in our app:

Step 6.(Optional) Reset all stacks
Using the concept of separate navigation trees inside a TabbedPage, you might also want to be able to reset all of them at once. For instance, if a user logs out, you would not want the app to keep all the navigation stacks for the next logged in user to see. What you would need here is to call PopToRootAsync() method on the Navigation property of each one of your TabbedPage children.

Step 7. (Optional) Set selected tab
Sometimes it can also be useful to define selected tab programmatically. In our case, a user should always land on a specific tab after login. To achieve this you can set the CurrentPage property of your TabbedPage.

I guess that would be it for our short dive into the unwonted peculiarities of cross-platform development. I hope this post was useful for some of you Xamarin warriors out there. Just like any of us here at Billige PlΓ€tze, I would be very happy to hear from you in the comment section below. Any feedback is appreciated.

Let’s learn from each other.
Dannynator.

]]>
https://craftcoders.app/xamarin-tabbed-page-navigation/feed/ 0