Old MvvmCross versions and Android 10 Play Store requirement

|

If you are looking for samples scroll down to the bottom of this blog post

I have gotten questions from multiple people, about versions of MvvmCross prior to version 6.4.1. What they can do about being forced to target Android 10, API 29, or newer from November 2nd, when Google stops accepting updates to Apps targeting lower API levels.

MvvmCross 6.4.1 introduced some changes to MvxLayoutInflater that are needed in order for it to work with Android 10. However, these changes are for obvious reasons not part of previous releases.

Luckily MvvmCross is written in a way where you can replace parts of it through virtual methods and implementing certain interfaces. This can be used to retrofit old versions of MvvmCross with newer patched versions of, in this case, MvxLayoutInflater.

You simply have to follow these 3 simple steps!

  1. Go to the corner
  2. Curl up in a ball
  3. Cry 😭

Sorry, wait, no. Thats not it! Upgrade MvvmCross! But, if you really cannot upgrade then try these steps.

  1. Grab latest MvxLayoutInflater
  2. Create your own implementation of MvxContextWrapper which uses 1.
  3. Create your own MvxActivity derivatives that uses ContextWrapper from 2.
  4. Replace all MvxActivities with your own

OK, I lied that was 4 steps. You might also need a 5th step, where you target Android 10 in your App, otherwise all this work will be useless anyways…

Lets go through all steps.

1. Get latest MvxLayoutInflater

Grab latest MvxLayoutInflater and MvxLayoutInflaterCompat, change namespace to something more suitable in your App. Rename the class FixedLayoutInflater or whatever you prefer.

2. Create your own implementation of MvxContextWrapper

We have to supply our own MvxContextWrapper class, the code in there is super simple. It just tells Android which LayoutInflater to use. We want to use our FixedLayoutInflater in there.

using System;
using Android.Content;
using Android.Runtime;
using Android.Views;
using MvvmCross.Binding.BindingContext;
using Object = Java.Lang.Object;

namespace Awesome.App
{
    [Register("awesome.app.FixedContextWrapper")]
    public class FixedContextWrapper : ContextWrapper
    {
        private LayoutInflater _inflater;
        private readonly IMvxBindingContextOwner _bindingContextOwner;

        public static ContextWrapper Wrap(Context @base, IMvxBindingContextOwner bindingContextOwner)
        {
            return new FixedContextWrapper(@base, bindingContextOwner);
        }

        protected FixedContextWrapper(Context context, IMvxBindingContextOwner bindingContextOwner)
            : base(context)
        {
            if (bindingContextOwner == null)
                throw new InvalidOperationException("Wrapper can only be set on IMvxBindingContextOwner");

            _bindingContextOwner = bindingContextOwner;
        }

        public override Object GetSystemService(string name)
        {
            if (string.Equals(name, LayoutInflaterService, StringComparison.InvariantCulture))
            {
                return _inflater ??=
                    new FixedLayoutInflater(LayoutInflater.From(BaseContext), this, null, false);
            }

            return base.GetSystemService(name);
        }
    }
}

3. Create your own MvxActivity derivate

Next step is to create your own MvxActivity derivate. Why? Because you need to supply that ContextWrapper we just created. If you were to simply inherit from MvxActivity and override OnAttachContext, which is where we supply the ContextWrapper, then it would still use the original MvxContextWrapper, since the only way to supply it is by calling base.OnAttachContext.

OK. So yank whatever MvxActivity type you are re-implementing. Here is a non-exhaustive list of types we had.

MvvmCross Version Type
4.4.0 MvxActivity
4.4.0 MvxAppCompatActivity
5.7.0 MvxActivity
5.7.0 MvxAppCompatActivity

The part to replace is the contents of OnAttachContext which should look something like:

protected override void AttachBaseContext(Context @base)
{
    if (this is IMvxAndroidSplashScreenActivity)
    {
        // Do not attach our inflater to splash screens.
        base.AttachBaseContext(@base);
        return;
    }
    base.AttachBaseContext(FixedContextWrapper.Wrap(@base, this));
}

With that done, now you just have to replace every inheritance from MvxActivity in your App with this implementation, except for any splash screens, not needed there.

You may have to think a bit here and modify the code to your needs, but these are the simplest steps I could come up with.

You can find sample projects with fixes implemented on my GitHub in the repository OldMvvmCross-Android10

MvvmCross Code Snippets

|

This blog post is a part of Louis Matos’s Xamarin Month, where this months topic is Code Snippets. For more information take a look at his blog and see the list of all the other auhtors who are participating. There will be a new post each day of the month, which is super cool!

Let me share some code snippets that I often use. All of these snippets will be available in my XamarinSnippets code repository on GitHub, for import in Visual Studio 2019, ReSharper, Rider and Visual Studio for Mac. Instructions provided in the repository Readme file.

When writing an Application using MvvmCross, or even with other frameworks, there are some pieces of code that you have to repeat again and again. As programmers we are usually a bit lazy and don’t want to type all that code over and over again. Fortunately, we can have code snippets ready to help us, a nice feature built into our IDE’s. Some of the snippets I use often are as follows.

mvxprop

I often have to create a property in my ViewModels which raise the PropertyChanged event in the MvvmCross flavor. For that I simply type mvxprop and press Tab, and magically I get the following code.

private int propertyName;
public int PropertyName
{
    get => propertyName;
    set => SetProperty(ref propertyName, value);
}

I have considered making some flavors for some common types that I use, such as string, bool, int. Not sure how much I would use them.

mvxcom

Creating commands is also something that I often do. However, I’ve gone away from using this pattern where I lazily initialize commands, some of you may find them useful. What I prefer instead is initialize them in the constructor of the ViewModel instead.

private MvxCommand _command;
public MvxCommand Command =>
    _command ??= new MvxCommand(DoCommand);

private void DoCommand()
{
    // do stuff
}

I have a couple of variants of this snippet, which creates different types of MvxCommand. mvxcomt for the generic MvxCommand<T>, mvxcomasync for the async version MvxAsyncCommand and similarly the generic version of that mvxcomtasync which creates a MvxAsyncCommand<T>.

mvxbset

Last but not least, a snippet for creating a binding set for binding views on both Android and iOS. I’ve taken the liberty to make the assumption that View and ViewModel names follow each other. So PeopleView will usually have a corresponding ViewModel PeopleViewModel. This ends up creating something like this:

using var set = this.CreateBindingSet<PeopleView, PeopleViewModel>();

Get all the snippets along with instructions in my XamarinSnippets repository on GitHub.

Do you have any cool related MvvmCross snippets to share? Put them in the comments or make a Pull Request on the repository.

Released a Color Picker Library

|

Re-signing a IPA file

|

I just had the task to figure out why we had an App crashing randomly on us all of the sudden. The App is distrubuted through AppCenter only, as a Enterprise build. Turned out that the provisioning profile had expired hence it started to fail.

Now, I did not want to build a new version of the App, since the one distributed and in production is much older and we are in the middle of moving CI for the App into a new enviroment. So the only option was to sign the App again with the new provisioning profile. So here are some notes on how I did that.

The prerequisites for this are:

  • IPA file to re-sign
  • Distribution certificate is installed in your KeyChain
  • mobileprovision file to sign with
  • a macOS installation with Xcode installed (I used 11.3.1)

The following snippets are commands you would run in your preferred commandline.

First we need to unizp the IPA file. It will contain a Payload folder with AppName.app inside, where AppName is your App’s name. I will use MyApp as an example throughout this post. Yours will of course be different.

unzip MyApp.ipa

You should now have a Payload folder where contents were extracted.

First we need to extract the entitlements for the App, we will need them later when we are signing the App again.

codesign -d --entitlements :- "Payload/MyApp.app" > entitlements.plist

This will create a entitlements.plist file in the folder you are currently in. If you are re-signing the App with a distribution certificate for another team, remember to change identifiers in the entitlements file to match your distribution certificate.

Now before we re-sign the App, we need to remove the old code signing.

rm -r Payload/MyApp.app/_CodeSignature

We also need to replace the provisioning profile. Assuming your profivisioning profile file name is called MyApp.mobileprovision and is located in the same folder we are in.

cp MyApp.mobileprovision Payload/MyApp.app/embedded.mobileprovision

We should now be ready to re-sign the Application.

codesign -f -s "iPhone Distribution: your team name here" --entitlements entitlements.plist Payload/MyApp.app

Now we just need to zip the folder and we are ready to distribute it.

zip -qr MyApp-resigned.ipa Payload

That is it! You are ready to ship your re-signed App!

Connecting to WiFi in Android 10

|

Android 10 was recently released and it introduces a bunch of changes in terms of Privacy. This means that access to /proc/net from the Linux sub-system has been restricted, which requires you to use NetworkStatsManager and ConnectivityManager to get VPN information.

It adds restrictions to who is allowed to enable/disable WiFi. We could previously use WifiManager.SetWifiEnabled(), but not anymore, this method will return false. You will need to show one of the new Settings Panels, which shows a slice of the Android Settings within your App.

What this post will focus on is the restrictions to access to configured networks and connecting to networks. A bunch of the network API has changed, so let us look a bit into what we have available now.

Suggesting networks

Something new to Android 10 is suggesting networks to connect to. These are just hints to the platform that the networks you provide it, can be connected to and it might decide to chose one of them. When any of these networks are detected nearby, Android will show a notification to the user the first time, which is how they allow connecting to a suggested network.

This could be useful for an Enterprise App, which can allow Access to networks depending on the logged in user or suggest a separate network for guests.

You can suggest networks like so.

var guestUsers = new WifiNetworkSuggestion.Builder()
    .SetSsid("GuestNetwork")
    .SetWpa2Passphrase("hunter2")
    .Build();

var secretEnterpriseNetwork = new WifiNetworkSuggestion.Builder()
    .SetSsid("Cyberdyne")
    .SetWpa2Passphrase(":D/-<")
    .Build();

var suggestions = new[] { guestUsers, secretEnterpriseNetwork };

var wifiManager = this.GetSystemService(Context.WifiService) as WifiManager;
var status = wifiManager.AddNetworkSuggestions(suggestions);

if (status == NetworkStatus.SuggestionsSuccess)
{
    // We added suggestions!
}

The suggestions you provide can only be added once. If you try add the same suggestion again, the status from AddNetworkSuggestion will return SuggestionsErrorAddDuplicate. If you need to modify a suggestion, you need to remove it first with  RemoveNetworkSuggestion, then add the modified version of it again. Additionally, in order to add these suggestions you will need to add the CHANGE_WIFI_STATE permission to your AndroidManifest.xml.

<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

Note: Some of the options on a WifiNetworkSuggestion requires you to request Fine Location permission in order to work. Make sure to consult the Android documentation to be sure.

After you run AddNetworkSuggestion don’t expect something to happen immediately. There will eventually be a notification in the notification drawer. Which looks like something in the image below. Choosing “Yes” on the notification, won’t necessarily automatically connect to the network. However, going to Wifi Settings on your device, it should now know how to connect to that network.

suggestion notificationsuggestion wifi settings

Connecting to Specific Networks

Prior to Android 10, we could explicitly tell Android to connect to a specific Network and it would simply do it. This was done using WifiManager.AddNetwork() where you provided a WifiConfiguration. Now this API has been deprecated and replaced with ConnectivityManager.RequestNetwork, where you build a Network Request with a Wifi Specification, similar to the suggestions I showed you above. However, it also allows you to specify, whether the connection requires Internet, cannot be VPN, has to be WiFi and more.

You will also need to provide a ConnectivityManager.NetworkCallback, which will let you know whether connection to the network was successful or not. The idea here is that you are awaiting the requested network to connect and only when that is successful you can continue with your execution.

This is excellent for scenarios, where you need to be on a specific network to do some specific operations. One thing to note is that you will only be able to connect to this network while the App is open. As soon as the App is closed, it disconnects the network.

Note: this connection will not have any Internet connectivity according to the Google docs. See the notes in the Android documentation about WiFi Bootstrapping. For Internet connectivity Google suggests to use the Network Suggestions described above.

Let us look at some code.

var specifier = new WifiNetworkSpecifier.Builder()
    .SetSsid("cyberdyne")
    .SetWpa2Passphrase("ill be back")
    .Build();

var request = new NetworkRequest.Builder()
    .AddTransportType(TransportType.Wifi) // we want WiFi
    .RemoveCapability(NetCapability.Internet) // Internet not required
    .SetNetworkSpecifier(specifier) // we want _our_ network
    .Build();

var connectivityManager = this.GetSystemService(Context.ConnectivityService) as ConnectivityManager;

connectivityManager.RequestNetwork(request, callback);

Here is an example of wanting to connect to the network with SSID “cyberdyne” and passphrase “ill be back”. The transport type specifies what kind of network you want to connect to. In this case it is a WiFi network. We do not require any Internet capability on the network. Per default it requests Internet, Not VPN and Trusted capabilities.

The callback looks something like this.

private class NetworkCallback : ConnectivityManager.NetworkCallback
{
    public Action<Network> NetworkAvailable { get; set; }

    public override void OnAvailable(Network network)
    {
        base.OnAvailable(network);
        NetworkAvailable?.Invoke(network);
    }
}

var callback = new NetworkCallback
{
    NetworkAvailable = network =>
    {
        // we are connected!
    }
};

You can also use OnUnavailable to detect that we could not connect to the network in particular, or the user pressed cancel.

Connect to Device Request

If you connect once to a network request, any subsequent requests will automatically connect if you are in range of the network and the user will not have accept it again. Also, while the App is open and you want to disconnect from the requested network, you simply call ConnectivityManager.UnregisterNetworkCallback() on the callback.

This hopefully gives you an idea about how to connect to WiFi Networks with Android 10.

You can check out the code from my Android 10 WiFi Repository on GitHub and have a go at playing with it yourself.