Saving Android scroll state in a Fragment

If you’ve architected an Android app as a single activity app switching fragments out for each screen, you’ll know that saveinstancestate doesn’t get called on the fragment. So how do you save the scroll position of a fragment you’ve just navigated away from when you navigate back to it (maybe from hitting the back button)? It’s actually very simple.

Fragments automatically save and restore the states of their Views, as long they have IDs assigned. If you ensure to assign an ID to your scrollview or nestedscrollview, its scroll state can be restored simply by doing:

<androidx.core.widget.NestedScrollView 
    android:id="@+id/myscrollview"
...>

Et voila!

Xamarin Native, Xamarin Forms and native mobile

I hear many misunderstandings of what Xamarin is and how it differs from native mobile.

There are two flavours of Xamarin: Xamarin Native and Xamarin Forms. They are very different.

The first offers a native mobile experience with the UI written in the native language for the device type. Therefore an understanding of the native tooling, languages, OSs and devices is required to do this type of mobile development. The advantage here is that Xamarin Native developers typically know native development but can also work across both iOS and Android in their respective native languages.

The second is an abstraction, does not have access to all native features and has more shared code between the device types than Xamarin Native. Forms is more suited to prototypes and line of business apps rather than consumer apps.

For an excellent overview of what Xamarin Native and Xamarin forms are and how they differ from native mobile, have a quick read of this:

https://www.altexsoft.com/blog/mobile/the-good-and-the-bad-of-xamarin-mobile-development/

 

Adding a spotlight overlay in Android

Often there's a need to provide an overlay to your view with a spotlight to highlight a particular area. These are commonly used in the context of map views or tutorials.

First subclass RelativeLayout:

public class MaskView extends RelativeLayout
{
Paint _paint;
Button _spotlightButton;
PorterDuffXfermode _blender;
LayoutParams _spotlightButtonLayout;
Matrix _matrix;
Context _context;
int _spotlightX;
int _spotlightY;
float _scale;
public MaskView(Context context)
{
super(context);
_context = context;
}
public MaskView(Context context, AttributeSet attrs)
{
super(context, attrs);
_context = context;
}
public MaskView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
_context = context;
}
}

Set your X,Y and set up your paint object:

public void init(Point point, String buttonText, float scale)
{
SetHardwareAccelerated(true);
_spotlightX = point.x;
_spotlightY = point.y;
_scale = scale;
_blender = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
_paint = new Paint();
_paint.setColor(getResources().getColor(android.R.color.white));
_paint.setAlpha(0);
_paint.setXfermode(_blender);
_paint.setAntiAlias(true);
addButton(buttonText);
}

Then override the dispatchDraw method and draw your circle:

@Override
protected void dispatchDraw(Canvas canvas)
{
canvas.drawColor(Color.parseColor("#8C000000"));
_matrix = new Matrix();
_matrix.postScale(_scale, _scale, _spotlightX, _spotlightY);
canvas.setMatrix(_matrix);
canvas.drawCircle(_spotlightX, _spotlightY, 200, _paint);
canvas.setMatrix(new Matrix());
super.dispatchDraw(canvas);
}

Using your new layout:

_maskView = new MaskView(this);
_maskView.init(new Point(width / 2, height / 2), "Dismiss mask view", 1f);
_maskView.getMaskButton().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view)
{
_maskView.setVisibility(View.GONE);
}
});

And that is it! Full code on GitHub at https://github.com/pricimus/Mask

Adding a spotlight overlay in iOS and Swift

Often there's a need to provide an overlay to your view with a spotlight to highlight a particular area. These are commonly used in the context of map views or tutorials.

First create your overlay UIView and set the background colour:

_overlay = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
 _overlay.backgroundColor = UIColor(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.5)
 view.addSubview(_overlay)

Create your circle:

let path = CGMutablePath()
path.addArc(center: CGPoint(x: view.frame.size.width / 2, y: view.frame.size.height / 2), radius: 80, startAngle: 0.0, endAngle: 2 * 3.14, clockwise: false)
path.addRect(CGRect(x: 0, y: 0, width: _overlay.frame.width, height: _overlay.frame.height))
Create your mask layer and set the path:
let maskLayer = CAShapeLayer()
maskLayer.backgroundColor = UIColor.black.cgColor
maskLayer.path = path;
maskLayer.fillRule = kCAFillRuleEvenOdd

Add the mask layer to the overlay:

_overlay.layer.mask = maskLayer
_overlay.clipsToBounds = true

And that is it! Full code on GitHub at https://github.com/pricimus/Mask-iOS

 

Using the iOS AVFoundation sdk in Xamarin to take a picture and save to the image gallery

Using the AVFoundation you can capture still images from your app, convert to a CIImage, obtain a mutable copy of its metadata (you can add custom properties), then save to the device's image gallery:

 

async void TakePhotoButtonTapped(UIButton sender)
{
var videoConnection = stillImageOutput.ConnectionFromMediaType(AVMediaType.Video);

var sampleBuffer = await stillImageOutput.CaptureStillImageTaskAsync(videoConnection);

var jpegImageAsNsData = AVCaptureStillImageOutput.JpegStillToNSData(sampleBuffer);

var image = CIImage.FromData(jpegImageAsNsData);

var metaData = image.Properties.Dictionary.MutableCopy() as NSMutableDictionary;

var library = new ALAssetsLibrary();

library.WriteImageToSavedPhotosAlbum(jpegImageAsNsData, metaData, (assetUrl, error) => 
{
Console.WriteLine ("assetUrl:"+assetUrl);
});

}

Moving the video seek bar in Xamarin UITest

Do you have videos in your app? Would you like to test the workflow surrounding your video using Xamarin UITest? Don't want to slow down your UI tests waiting for the video to play? Then just fastforward through the video play:

public class VerifyVideoCommand
    {
        readonly IApp _app;
        readonly IWelcomeScreen _welcomeScreen;

        public VerifyVideoCommand ()
        {
            _app = FeatureContext.Current.Get<IApp> ("App");
            _welcomeScreen = FeatureContext.Current.Get<IWelcomeScreen> (ScreenNames.WelcomeScreen);
        }

        public void Execute ()
        {
            //Tap the videoView to show the seekbar
            _app.WaitForElement (_welcomeScreen.VideoView, timeout: TimeConstants.TwentySeconds);
            _app.Tap (_welcomeScreen.VideoView);

            //Move the seekbar
            _app.Tap (_welcomeScreen.VideoView);
            _app.WaitForElement(_welcomeScreen.VideoSeekBar, timeout: TimeConstants.TwentySeconds);
            var slider = _app.Query (_welcomeScreen.VideoSeekBar).First ();
            _app.TapCoordinates (slider.Rect.Width, slider.Rect.CenterY);
        }
    }

Using AIDL files in Xamarin

 

If you ever need to use inter process communication you have the option of using AIDL (Android Interface Definition Language). To do so, import your AIDL file(s) into your Xamarin solution. Define your class that will call the service described by the aidl file. Init an intent to bind to the Service. Then implement a ServiceConnection class that inherits from java.lang.Object and implements IServiceConnection.


using System;
using Android.Content;
using Aidlservice;
using Android.OS;
using Android.App;

namespace AidlExample
{
    public class MyClass
    {

        IWoyouService woyouService;
        WoyouServiceConnection serviceConn;

        public void Init(Context context)
        {
            serviceConn = new WoyouServiceConnection();

            var intent = new Intent();
            intent.SetPackage("aidlservice");

            intent.SetAction("aidlservice.IWoyouService");
            context.StartService(intent);
            context.BindService(intent, serviceConn, Bind.AutoCreate);
        }

        public void DoSomething(String msg, ICallback callback)
        {
            woyouService = serviceConn.GetService();

            if (woyouService != null)
            {
                try
                {
                    woyouService.MyMethod(msg, callback);
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.Print(string.Format("Error: {0}", ex.Message));
                }
            }
        }
    }

    public class WoyouServiceConnection : Java.Lang.Object, IServiceConnection
    {
        public new void Dispose()
        {
            base.Dispose();
        }

        public IWoyouService GetService()
        {
            return woyouService;
        }

        IWoyouService woyouService = null;

        public void OnServiceDisconnected(ComponentName name)
        {
            woyouService = null;
        }        

        public void OnServiceConnected(ComponentName name, IBinder service)
        {
            woyouService = IWoyouServiceStub.AsInterface(service);
        }
    }

}
 

And remember to set the build action of the AIDL files to AndroidInterfaceDescription