WIFI QR Code Scanner With Camera X & Google ML Kit

Ahmed Samir
7 min readMar 3, 2022

--

Free WIFI? Hurray!

You might have been to a restaurant or some public place before and found this paper hung on their walls or placed on tables. Well, so have I, when I was at the university last week I connected to their WIFI by scanning the QR code and at that moment I got an idea to implement this feature in an existing app.
You may wonder that we already have a third-party or OS apps that can handle this kind of process neatly with saving networks configuration and so (we are gonna discuss this later in the article), but doesn’t it itches you to know how would this functionality work somehow? It does itch me

CameraX

A Jetpack library that simplifies Camera driven apps development and supports different Android versions starting with Android 5.0 (API level 21) see more.

ML Kit

A machine learning mobile SDK that is maintained and developed by Google helps mobile developers to integrate machine learning into their apps. If you’ve ever used Mobile Vision API, you’ll be familiar with ML kit as Mobile Vision API has been deprecated and it’s now part of the ML SDK.

Alright, let’s start coding.

First with the dependencies:

Make sure to have the same CameraX version among CameraX dependencies

And since we’re using compose we are going to use Accompanist library which simplifies commonly required tasks made with compose such as permissions requesting and handling in our case.

Do not worry if you’re using XML still, all that you’ll find here rather than UI and permissions handling is quite the same for XML so you’ll do okay with adding only CameraX & ML kit dependencies.

Do not forget to add the permissions as well in AndroidManifest.xml file:

And now let’s start with the exciting stuff

So first we’re going to create QRCodeWIFiAnalyzer class that has the functionality to analyze QR codes images:

No! Don’t feel overwhelmed, it is easy once you get it, thanks to ML kit, implementing this is much easier than other libraries like Zxing Decoder.

Now, what’s happening? in the class constructor, we have 2 callbacks which are going to be invoked once we capture the result, or fail while doing so, and we implement Analyzer Interface from ImageAnalysis class which is considered as a UseCase for our Camera (we are going to see this later in the article) then we create a scanner object and specify the Barcode format that we want to use.

Note: You can skip specifying the Barcode format to make the scanner more flexible to scan different Barcode formats.

By implementing ImageAnalysis we got to override analyze function and there all the magic happens.

We first get the image captured from the camera preview by ImageProxy wrapper class image property, then we obtain ImageInput object from fromMediaImage function to pass it to the scanner to analyze it right after.

Now we check every barcode we got from the success listener. We’ll check its type, then get the SSID which resembles the access point or the WIFI name and the password, and once we’re done we send them to onSuccess lambda function. We could also put failure listener to catch exceptions regarding the scanning process and most importantly do not forget to close the image proxy in the complete listener to tell the analyzer that we are ready to analyze new images received from the camera.

Now let’s start handling the request for runtime permission for the camera. XML buddies, skip this step and do your usual handling as normal.

Here we are just making PermissionRequest composable with the parameter needed to request for a particular permission, then we make permissionState that observes permission results which we are going to use to handle them accordingly.

Then we make DisposableEffect to only request the permission when the Activity lifecycle is onStart. DisposableEffect is considered to be a Side-effect that gives you a controlled environment to run side-effect code that may interfere with the recomposition of a composable with giving respect to the lifecycle of composables. For more information take a look at side-effects here.

Now in short, if the user accepted the permission then we call the content composables, and if he didn’t then we start showing the rational dialog composable to tell the user that this permission is essential for the app to function properly.

Discussing permissions handling with Accompanist library is behind the scope of this article, but if you want to learn more about it then click here.

CameraX doesn’t have composables support yet, so we will convert the XML view by using AndroidView composable. For XML users again, you can stick to the <PreviewView> to learn more go through this rich cameraX codelab.

We first define the scale type used for our PreviewView to render, camera selector, to choose the preferable camera (front or back), and the same two lambda functions we used previously for our QRCodeWIFIAnalyzer class.

Start creating the PreviewView composable by creating an instance of PreviewView class and specifying its scale type and its layout parameters.

Next, we start making a previewUseCase object to build up our preview use case which will then be used to render previews from the camera and show it on the user screen, but before this, we should set a surface for it the be displayed on, so we get the surfaceProvider from the previously initialized preview view. By setting the previewUseCase surfaceProvider, the provider will tell the camera that previewUseCase is ready to get preview stream data to show it on screen.

ImageAnalysis is another use case that we will use to make our app able to analyze the images received from the camera. So we’ll build our imageAnalysis object by setting the resolution, backpressureStrategy (which tells the camera what to do if it receives multiple images at once, and here we’re telling it to keep only the most received one), and finally our analyzer class.

ProcessCameraProvider will help us bind the lifecycle of our camera to any LifecycleOwner, binding the lifecycle will determine when the camera should start & stop according to the state of the lifecycle. So first we got to unbind all the owners in case if we will rebind them again when we for example rotate the screen. Rebinding again without unbinding will stop the data stream coming from the camera. Bind the camera using bindToLifecycle function and then pass the owner, cameraSelector, previewUseCase, imageAnalysis (AnalyzerUseCase) object.

So, now we’re done with the exciting stuff and we’ll dive into a brief introduction to 3 APIs to configure WIFI networks in Android.

WifiConfiguration

WifiConfiguration API can be used to configure a network, it was deprecated in Android API level 29 so it is no longer maintained but we are still going to use it for Android versions below 29.

WifiNetworkSpecifier

We can use this API to specify and request a WIFI network. Implementing this API is quite easy and similar to WifiConfiguration API. Although, it restricts the connection for the current app, meaning that once you leave the app, the connection is lost. As well as there will not be any internet connection unless you bind the network to the app process with ConnectivityManager API.

WifiNetworkSuggestion

WifiNetworkSuggestion API is used to provide WIFI networks suggestions or consideration and it proposes those networks to the user. It is considered to be the replacement for WifiConfiguration for Android 10 (API level 29) and above, though it lacks some options that we had in WifiConfiguration for security concerns. Nevertheless, we are going to use it as it binds the network and provides an internet connection automatically even after leaving the app, but the network will still be bounded to our app and once we delete the app the OS forgets our network, hence the network will not be saved.

Here we’re going to implement two functions that we’ll use to access a WIFI network. We could simply put those functions in a separate file to increase the readability and separate the concerns, but we’ll place them on MainActivity.

connectBelow29 & connect29AndAbove functions will be used to connect with Android API below 29, 29, or later respectively. Let’s start looking at connectBelow29, we first start checking if the WIFI is enabled through isWifiEnabled from WifiManager class and if so we proceed to connect to the network, else we enable the wifi. Once done, we create a WifiConfiguration object and set its SSID and pre-shared key, both surrounded by quotations. Additionally, we add the network to the WifiManager object by using addNetwork function which returns the network Id. Eventually, we disconnect the user from any network that he/she might be connected to, enable our desired network, and reconnect.

For Android Q (API 29) and above we use connect29AndAbove function, we also start checking the WIFI if it’s enabled, but if it is not, then we launch an Intent to let the user enable it by himself/herself in this case, as setWifiEnabled function is deprecated and won’t work for our Android Q devices. After that, we build our WifiNetworkSuggestion instance which is similar to building a WifiConfiguration object. Then, we remove the suggestion network from the WifiManager in case we were connected to it or connected to it via the app scanner and forgot it from the WIFI picker. We are almost done, all we got to do is to add the network suggestion back to the WifiManager, and additionally, we make and register a broadcast receiver which will be called once we connect to the network successfully.

Here’s the full code of the MainActivity:

And yes before I forget here’s the function I used in MainActivity for checking the Android version:

Get the full project from here

Connecting to a WIFI network with Android 29 and above programmatically is a headache and it is not ideal, even my solution here is not the perfect one. We all wish we could use WifiConfiguration which used to do the job seamlessly and made our life easier.

--

--