Xamarin.Forms – Mock Locations

This Project you can find at: https://github.com/officialdoniald/Xamarin.Forms.MockLocation.

Forms:

We can detect Location changing in the Forms Project: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Mobile/Xamarin.Forms.MockLocation.Mobile/MainPage.xaml.cs. If we will get a Mock Location this will detect it.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.Essentials;
using Xamarin.Forms;

namespace Xamarin.Forms.MockLocation.Mobile
{
    // Learn more about making custom code visible in the Xamarin.Forms previewer
    // by visiting https://aka.ms/xamarinforms-previewer
    [DesignTimeVisible(false)]
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();

            Thread thread = new Thread(async () =>
            {
                while (true)
                {
                    var request = new GeolocationRequest(GeolocationAccuracy.Medium);
                    var location = await Geolocation.GetLocationAsync(request);

                    if (location != null)
                    {
                        if (location.IsFromMockProvider)
                        {
                            //Detect mocking location
                        }
                        else
                        {
                            //Detect normal location
                        }
                    }
                }
            });
                    }
    }
}

The two platforms need to be discussed separately. In Android we can make an Implementation for Mock Location, but in iOS, we have to use a script file and we have to add to the emulator, so we can’t make this programmatically.

Android:

First, we have to request permissions in the Manifest and in the MainActivity (Runtime).

Manifest: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Mobile/Xamarin.Forms.MockLocation.Mobile.Android/Properties/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.xamarin.forms.mocklocation.mobile">
  <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
  <application android:label="Xamarin.Forms.MockLocation.Mobile.Android"></application>
  
  <uses-permission android:name="android.permission.PRIORITY_HIGH_ACCURACY" />
  <uses-feature android:name="android.hardware.location" android:required="false" />
  <uses-feature android:name="android.hardware.location.gps" android:required="false" />
  <uses-feature android:name="android.hardware.location.network" android:required="false" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
  <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

MainActivity: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Mobile/Xamarin.Forms.MockLocation.Mobile.Android/MainActivity.cs

protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(savedInstanceState);

            Context = this.ApplicationContext;

            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);

            ActivityCompat.RequestPermissions(this, new String[] { Manifest.Permission.AccessFineLocation }, 1);
            
            LoadApplication(new App());
        }

Allright! We have to send the mock(test) location via LocationManager. Let’s create an interface in the Forms Project and call the Android implementation via DependencyService.

Forms: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Implementation/IMockLocationProvider.cs.

namespace Xamarin.Forms.MockLocation.Implementation
{
    public interface IMockLocationProvider
    {
        void SendMockLocation(MockPosition mockPosition);
    }

    public class MockPosition
    {
        public double Longitude { get; set; }

        public double Latitude { get; set; }

        /// <summary>
        /// Default value: 1.0.
        /// </summary>
        public double Altitude { get; set; } = 1.0;
    }
}

Android: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Mobile/Xamarin.Forms.MockLocation.Mobile.Android/Implementations/MockLocationProvider.cs.

using System;
using Android.Content;
using Android.Hardware;
using Android.Locations;
using Xamarin.Forms.MockLocation.Implementation;
using Xamarin.Forms.MockLocation.Mobile.Droid.Implementations;

[assembly: Xamarin.Forms.Dependency(typeof(MockLocationProvider))]
namespace Xamarin.Forms.MockLocation.Mobile.Droid.Implementations
{
    public class MockLocationProvider : IMockLocationProvider
    {
        public void SendMockLocation(MockPosition position)
        {
            Location location = new Location(LocationManager.GpsProvider)
            {
                Latitude = position.Latitude,
                Longitude = position.Longitude,
                Altitude = position.Altitude,
                Time = DateTime.Now.Ticks,
                ElapsedRealtimeNanos = 100,
                Speed = 0.0f,
                Bearing = 0.0f,
                Accuracy = 0
            };

            LocationManager locationManager = MainActivity.Context.GetSystemService(Context.LocationService) as LocationManager;

            locationManager.AddTestProvider(LocationManager.GpsProvider, false, false, false, false, false, false, false, Power.Low, SensorStatus.AccuracyHigh);
            locationManager.SetTestProviderLocation(LocationManager.GpsProvider, location);
            locationManager.SetTestProviderEnabled(LocationManager.GpsProvider, true);
        }
    }
}

Deploy your app to the phone and close it.

One more important thing: only the System app can send mock locations, so we have to call for the operation system, that this will send mock locations. So if you haven’t already, let’s enable developer mode on your phones and go to developer options.

Select the Mock Location, and select you app. Run the app again and it will work.

iOS:

So, as above said, we can mock the Location programmatically. We have to create a script fiel and add it to the Emulator. Here is a good website for the gpx file generating: https://www.gpxgenerator.com/.

<?xml version="1.0"?>
<gpx version="1.1" creator="gpxgenerator.com">
<wpt lat="50.84633694747007" lon="4.364619255065918">
    <time>2017-02-10T10:48:50Z</time>
</wpt>
<wpt lat="50.845889847299254" lon="4.36431884765625">
    <time>2017-02-10T10:49:28Z</time>
</wpt>
<wpt lat="50.84507692692001" lon="4.363868236541748">
    <time>2017-02-10T10:50:36Z</time>
</wpt>
<wpt lat="50.84460271682405" lon="4.363675117492676">
    <time>2017-02-10T10:51:14Z</time>
</wpt>
<wpt lat="50.84374912650492" lon="4.363245964050293">
    <time>2017-02-10T10:52:25Z</time>
</wpt>
<wpt lat="50.84296326764228" lon="4.36281681060791">
    <time>2017-02-10T10:53:31Z</time>
</wpt>
<wpt lat="50.84281578200512" lon="4.36181902885437">
    <time>2017-02-10T10:54:22Z</time>
</wpt>
<wpt lat="50.84310709422066" lon="4.360542297363281">
    <time>2017-02-10T10:55:30Z</time>
</wpt>
<wpt lat="50.84268028733167" lon="4.359984397888184">
    <time>2017-02-10T10:56:13Z</time>
</wpt>
<wpt lat="50.84245672026023" lon="4.359222650527954">
    <time>2017-02-10T10:56:55Z</time>
</wpt>
<wpt lat="50.843019023277556" lon="4.35808539390564">
    <time>2017-02-10T10:58:07Z</time>
</wpt>
<wpt lat="50.84339840461739" lon="4.357527494430542">
    <time>2017-02-10T10:58:48Z</time>
</wpt>
<wpt lat="50.84356777029096" lon="4.357130527496338">
    <time>2017-02-10T10:59:12Z</time>
</wpt>
<wpt lat="50.844069089081536" lon="4.356368780136108">
    <time>2017-02-10T11:00:07Z</time>
</wpt>
<wpt lat="50.84437550047113" lon="4.3559181690216064">
    <time>2017-02-10T11:00:40Z</time>
</wpt>
<wpt lat="50.84555269015878" lon="4.354877471923828">
    <time>2017-02-10T11:02:27Z</time>
</wpt>
<wpt lat="50.84620302096338" lon="4.354770183563232">
    <time>2017-02-10T11:03:18Z</time>
</wpt>
<wpt lat="50.8467178597545" lon="4.354383945465088">
    <time>2017-02-10T11:04:03Z</time>
</wpt>
<wpt lat="50.84665428412764" lon="4.3536436557769775">
    <time>2017-02-10T11:04:40Z</time>
</wpt>
<wpt lat="50.84662380036855" lon="4.353042840957642">
    <time>2017-02-10T11:05:10Z</time>
</wpt>
<wpt lat="50.846430736098434" lon="4.3524473905563354">
    <time>2017-02-10T11:05:43Z</time>
</wpt>
<wpt lat="50.846213961280625" lon="4.351991415023804">
    <time>2017-02-10T11:06:11Z</time>
</wpt>
<wpt lat="50.84603783095749" lon="4.3516212701797485">
    <time>2017-02-10T11:06:34Z</time>
</wpt>
<wpt lat="50.845837989977895" lon="4.351256489753723">
    <time>2017-02-10T11:06:58Z</time>
</wpt>
<wpt lat="50.84563476098514" lon="4.350859522819519">
    <time>2017-02-10T11:07:23Z</time>
</wpt>
<wpt lat="50.84547556432245" lon="4.350618124008179">
    <time>2017-02-10T11:07:40Z</time>
</wpt>
<wpt lat="50.84531975429677" lon="4.350387454032898">
    <time>2017-02-10T11:07:56Z</time>
</wpt>
<wpt lat="50.845167330942075" lon="4.350119233131409">
    <time>2017-02-10T11:08:14Z</time>
</wpt>
<wpt lat="50.84499536286211" lon="4.3499743938446045">
    <time>2017-02-10T11:08:29Z</time>
</wpt>
</gpx>

If we get a gpx file, we have to script the Emulator. Open the AppleScript App on the Mac and create a new script:

on replace_chars(this_text, search_string, replacement_string)
    set AppleScript's text item delimiters to the search_string
    set the item_list to every text item of this_text
    set AppleScript's text item delimiters to the replacement_string
    set this_text to the item_list as string
    set AppleScript's text item delimiters to ""
    return this_text
end replace_chars
 
on run arg
--> arg is a collection of the command line parameters
    
    set xmlPathParam to item 1 of arg --> first param is the location of the gpx file
    set sleepParam to item 2 of arg --> second param is the time (seconds) to sleep between each iteration
 
    tell application "System Events"
        tell XML file xmlPathParam
            tell XML element "gpx"
                set wptElements to every XML element whose name = "wpt" -->put wpt elements in a collection for later use
            end tell
    end tell
    
    tell process "Simulator"
        set frontmost to true
            repeat with c from 1 to count of wptElements
                set wptElement to item c of wptElements
                tell wptElement
                    set lon to value of XML attribute "lon" of wptElement --> putting the lon value of the wpt element in a variable for later use
                    set lat to value of XML attribute "lat" of wptElement --> putting the lan value of the wpt element in a variable for later use
                end tell
                    
                click menu item "Custom Location…" of menu of menu item "Location" of menu "Debug" of menu bar 1
                set popup to window "Custom Location"
                set value of text field 1 of popup to my replace_chars(lat, ".", ",")
                set value of text field 2 of popup to my replace_chars(lon, ".", ",")
                click button "OK" of popup
                    
                delay sleepParam --> sleep to simulate 'natural' movement
            end repeat
        end tell
    end tell
end run

With this script this will open our gpx file and processes it.

iOS Blog Source: https://blog.pieeatingninjas.be/2017/02/10/custom-location-and-movement-on-xamarin-remoted-ios-simulator-for-windows/.

Another way: you can create a Xamarin UI Test Project and you can mock the location too. Just call the SetLocation function. (On Android it is still required that change the Mock Location app and request permissions.)

Xamarin.Forms Twilio implementation

This project you can find at: https://github.com/officialdoniald/Xamarin.Forms.Twilio

Twilio created just for Android and iOS libs and not for Xamarin. So we need to create Bindings for Xamarin.Forms and native.

First step: we need to create Android Binding Library(https://github.com/officialdoniald/Xamarin.Forms.Twilio/tree/master/Xamarin.Forms.Twilio.PoC/Xamarin.Android.Twilio.Client) and iOS Binding Library(https://github.com/officialdoniald/Xamarin.Forms.Twilio/tree/master/Xamarin.Forms.Twilio.PoC/Xamarin.iOS.Twilio.Client) in an Emptys Solution. We have to add the .aar/.jar file(to Jars folder to Android) and .a files (as native reference to iOS) to the Projects. But if you want to just using the completed DLL, just download theese Project from my GitHub, Clean and Rebuild and use the builded DLLs or just added theese Projects to your Application Project as Reference.

After this we need to create a Xamarin.Forms Application(https://github.com/officialdoniald/Xamarin.Forms.Twilio/tree/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC).

We need to add the DLLs or the Projects as Reference to the various Platforms Projects.

Super! We can use the Twilio lib from the Android and iOS Projects. But if we want to use theese from the Forms Application we need to create some Platform Specific implementations and we will call theese throught DependencyService.

Let’s create an Interface in the Forms Application: https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC/Implementation/Twilio/ITwilioClient.cs

Now we have to create some events. We will implement the MakeOutBoundCall, Mute and HangOut functions from Twilio. https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC/Helper/GlobalEvents.cs

Great! Now just implement theese in the various Platforms and we will talk about what have to do, when we want to use theese functions without crash.

Android:

Let’s create the interface imaplementation: https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC.Android/Implementation/Twilio/TwilioClient.cs

Open the MainActivity.cs and create the Twilio object.: https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC.Android/MainActivity.cs

Very important: we need to change our AndroidManifest.xml file: https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC.Android/Properties/AndroidManifest.xml. We need to add some uses and service.

Done!

iOS:

https://github.com/officialdoniald/Xamarin.Forms.Twilio/tree/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC.iOS

Don’t worry, if there will be some errors, that’s a Visual Studio bug. The project will be built.

Let’s create the interface imaplementation: https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC.iOS/Implementation/Twilio/TwilioClient.cs

Open the AppDelegate.cs file: https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC.iOS/AppDelegate.cs

And here we will use the microphone we have to change the Info.plist file: https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC.iOS/Info.plist

    <key>NSMicrophoneUsageDescription</key>
    <string>YOUR_APP_NAME will use your microphone, when you call someone.</string>

When you make an OutBoundCall you can use a lots of combination of the parameters. See some of theese at: https://www.twilio.com/docs/voice/client/tutorials/outgoing-calls.

Xamarin.Forms save and open PDF file

This project you can find at: https://github.com/officialdoniald/Xamarin.Forms.Save.Open.PDF.

It could be a real situation that your app need to save or open a pdf file. How can it possible? Throught DependencyService. There are many companies, who deal with generationg PDF Document in Xamarin.Forms. We need to create a PDF Document first and than save it. But if you want to open an exists document, just read this blog post cointinue :D. Companies, whom you can download NuGet Packages to create PDF Document:

First thing we need to do, that create an interface in the StandardLibrary. https://github.com/officialdoniald/Xamarin.Forms.Save.Open.PDF/blob/master/Xamarin.Forms.Save.Open.PDF/Xamarin.Forms.Save.Open.PDF/IPDFSaveAndOpen.cs

using System;
using System.IO;
using System.Threading.Tasks;

namespace Xamarin.Forms.Save.Open.PDF
{
    public interface IPDFSaveAndOpen
    {
        Task SaveAndView(string fileName, String contentType, MemoryStream stream, PDFOpenContext context);
    }

    /// <summary>
    /// Where should the PDF file open. In the app or out of the app.
    /// </summary>
    public enum PDFOpenContext
    {
        InApp,
        ChooseApp
    }
}

Implement it on the various platforms!

Android

https://github.com/officialdoniald/Xamarin.Forms.Save.Open.PDF/blob/master/Xamarin.Forms.Save.Open.PDF/Xamarin.Forms.Save.Open.PDF.Android/PDFSaveAndOpen.cs

using Android;
using Android.Content;
using Android.Content.PM;
using Android.Support.V4.App;
using Android.Support.V4.Content;
using Android.Webkit;
using Java.IO;
using System;
using System.IO;
using System.Threading.Tasks;
using Android.App;
using Xamarin.Forms;
using Xamarin.Forms.Save.Open.PDF;
using Droid.Implementations;

[assembly: Dependency(typeof(PDFSaveAndOpen))]
namespace Droid.Implementations
{
    public class PDFSaveAndOpen : IPDFSaveAndOpen
    {
        public async Task SaveAndView(string fileName, String contentType, MemoryStream stream, PDFOpenContext context)
        {
            string exception = string.Empty;
            string root = null;

            if (ContextCompat.CheckSelfPermission(Forms.Context, Manifest.Permission.WriteExternalStorage) != Permission.Granted)
            {
                ActivityCompat.RequestPermissions((Activity)Forms.Context, new String[] { Manifest.Permission.WriteExternalStorage }, 1);
            }
            
            if (Android.OS.Environment.IsExternalStorageEmulated)
            {
                root = Android.OS.Environment.ExternalStorageDirectory.ToString();
            }
            else
                root = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

            Java.IO.File myDir = new Java.IO.File(root + "/PDFFiles");
            myDir.Mkdir();

            Java.IO.File file = new Java.IO.File(myDir, fileName);

            if (file.Exists()) file.Delete();

            try
            {
                FileOutputStream outs = new FileOutputStream(file);
                outs.Write(stream.ToArray());

                outs.Flush();
                outs.Close();
            }
            catch (Exception e)
            {
                exception = e.ToString();
            }

            if (file.Exists() && contentType != "application/html")
            {
                string extension = MimeTypeMap.GetFileExtensionFromUrl(Android.Net.Uri.FromFile(file).ToString());
                string mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
                Intent intent = new Intent(Intent.ActionView);
                intent.SetFlags(ActivityFlags.ClearTop | ActivityFlags.NewTask);
                Android.Net.Uri path = FileProvider.GetUriForFile(Forms.Context, Android.App.Application.Context.PackageName + ".provider", file);
                intent.SetDataAndType(path, mimeType);
                intent.AddFlags(ActivityFlags.GrantReadUriPermission);

                switch (context)
                {
                    case PDFOpenContext.InApp:
                        Forms.Context.StartActivity(intent);
                        break;
                    case PDFOpenContext.ChooseApp:
                        Forms.Context.StartActivity(Intent.CreateChooser(intent, "Choose App"));
                        break;
                    default:
                        break;
                }
            }
        }
    }
}

iOS

We need to create a PreviewController and that we have to show the PDF on this Controller.

https://github.com/officialdoniald/Xamarin.Forms.Save.Open.PDF/blob/master/Xamarin.Forms.Save.Open.PDF/Xamarin.Forms.Save.Open.PDF.iOS/PreviewControllerDS.cs

using Foundation;
using QuickLook;
using System;
using System.IO;

namespace Xamarin.Forms.Save.Open.PDF.iOS
{
    public class PreviewControllerDS : QLPreviewControllerDataSource
    {
        //Document cache
        private QLPreviewItem _item;

        //Setting the document
        public PreviewControllerDS(QLPreviewItem item)
        {
            _item = item;
        }

        //Setting document count to 1
        public override nint PreviewItemCount(QLPreviewController controller)
        {
            return 1;
        }

        //Return the document
        public override IQLPreviewItem GetPreviewItem(QLPreviewController controller, nint index)
        {
            return _item;
        }
    }

    public class QLPreviewItemFileSystem : QLPreviewItem
    {

        string _fileName, _filePath;

        //Setting file name and path
        public QLPreviewItemFileSystem(string fileName, string filePath)
        {
            _fileName = fileName;
            _filePath = filePath;
        }

        //Return file name
        public override string ItemTitle
        {
            get
            {
                return _fileName;
            }
        }

        //Retun file path as NSUrl
        public override NSUrl ItemUrl
        {
            get
            {
                return NSUrl.FromFilename(_filePath);
            }
        }
    }

    public class QLPreviewItemBundle : QLPreviewItem
    {
        string _fileName, _filePath;

        //Setting file name and path
        public QLPreviewItemBundle(string fileName, string filePath)
        {
            _fileName = fileName;
            _filePath = filePath;
        }

        //Return file name
        public override string ItemTitle
        {
            get
            {
                return _fileName;
            }
        }

        //Retun file path as NSUrl
        public override NSUrl ItemUrl
        {
            get
            {
                var documents = NSBundle.MainBundle.BundlePath;
                var lib = Path.Combine(documents, _filePath);
                var url = NSUrl.FromFilename(lib);
                return url;
            }
        }
    }
}

Implementation:

https://github.com/officialdoniald/Xamarin.Forms.Save.Open.PDF/blob/master/Xamarin.Forms.Save.Open.PDF/Xamarin.Forms.Save.Open.PDF.iOS/PDFSaveAndOpen.cs

using System;
using System.Threading.Tasks;
using System.IO;
using Xamarin.Forms;
using UIKit;
using QuickLook;
using Xamarin.Forms.Save.Open.PDF.iOS;

[assembly: Dependency(typeof(PDFSaveAndOpen))]
namespace Xamarin.Forms.Save.Open.PDF.iOS
{
    public class PDFSaveAndOpen : IPDFSaveAndOpen
    {
        //Method to save document as a file and view the saved document
        public async Task SaveAndView(string filename, string contentType, MemoryStream stream, PDFOpenContext context)
        {
            //Get the root path in iOS device.
            string path = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
            string filePath = Path.Combine(path, filename);

            //Create a file and write the stream into it.
            FileStream fileStream = File.Open(filePath, FileMode.Create);
            stream.Position = 0;
            stream.CopyTo(fileStream);
            fileStream.Flush();
            fileStream.Close();

            //Invoke the saved document for viewing
            UIViewController currentController = UIApplication.SharedApplication.KeyWindow.RootViewController;
            while (currentController.PresentedViewController != null)
                currentController = currentController.PresentedViewController;
            UIView currentView = currentController.View;

            QLPreviewController qlPreview = new QLPreviewController();
            QLPreviewItem item = new QLPreviewItemBundle(filename, filePath);
            qlPreview.DataSource = new PreviewControllerDS(item);

            currentController.PresentViewController(qlPreview, true, null);
        }
    }
}

Let’s just create a Page and try it: https://github.com/officialdoniald/Xamarin.Forms.Save.Open.PDF/blob/master/Xamarin.Forms.Save.Open.PDF/Xamarin.Forms.Save.Open.PDF/MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Xamarin.Forms.Save.Open.PDF.MainPage">
    <StackLayout>
        <Button HeightRequest="100" Text="Save and open PDF in app" Clicked="Button_Clicked"/>
        <Button HeightRequest="100" Text="Save and open PDF with choose app" Clicked="Button_Clicked_1"/>
    </StackLayout>
</ContentPage>

https://github.com/officialdoniald/Xamarin.Forms.Save.Open.PDF/blob/master/Xamarin.Forms.Save.Open.PDF/Xamarin.Forms.Save.Open.PDF/MainPage.xaml.cs

using System;
using System.IO;

namespace Xamarin.Forms.Save.Open.PDF
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Save and open the PDF file in the app.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void Button_Clicked(object sender, EventArgs e)
        {
            MemoryStream stream = new MemoryStream();

            await Xamarin.Forms.DependencyService.Get<IPDFSaveAndOpen>().SaveAndView(Guid.NewGuid() + ".pdf", "application / pdf", stream, PDFOpenContext.InApp);
        }

        /// <summary>
        /// Save and open the PDF file with "choose app".
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void Button_Clicked_1(object sender, EventArgs e)
        {
            MemoryStream stream = new MemoryStream();

            await Xamarin.Forms.DependencyService.Get<IPDFSaveAndOpen>().SaveAndView(Guid.NewGuid() + ".pdf", "application / pdf", stream, PDFOpenContext.ChooseApp);
        }
    }
}

Why is just a MemoryStream stream = new MemoryStream(); ?

Because I don’t have a pdf in stream. 😀 This is your task to create a stream (upload a pdf on your phone and create a Stream: pdf files are opened in the same method like other file types) and open it with this method.

Xamarin.Forms – Android – change the Theme at runtime

This project you can find at: https://github.com/officialdoniald/Xamarin.Forms—Change-Android-Theme-at-runtime

In the .NET Standard/PCL Project we can’t say that, please change the Theme right now, because wehave to implement some platform specific implementation in the Android project.

How can the two project communicate? Now, we will see another way, unlike the previous ones, we will use events, not dependency service.

So, let’s create our event class in the .NET Standard/PCL Project (https://github.com/officialdoniald/Xamarin.Forms—Change-Android-Theme-at-runtime/blob/master/XamarinFormsChangeAndoirdThemeRuntime/XamarinFormsChangeAndoirdThemeRuntime/GlobalEvents.cs):

using System;
namespace XamarinFormsChangeAndoirdThemeRuntime
{
public class GlobalEvents
{
public static event EventHandler OnAndroidThemeChangeNeeded;
    
public static void OnAndroidThemeChangeNeeded_Event(object sender, int themeid)
    {
        OnAndroidThemeChangeNeeded?.Invoke(sender, themeid);
    }
   }
}

In the Android site, we will use the SetTheme(themeid) function, that set the Theme on the Application. We have to subscribe int the event int the MainActivity, and we have to call the SetTheme(themeid) method, in the main thread (https://github.com/officialdoniald/Xamarin.Forms—Change-Android-Theme-at-runtime/blob/master/XamarinFormsChangeAndoirdThemeRuntime/XamarinFormsChangeAndoirdThemeRuntime.Android/MainActivity.cs):

using Android.App;
using Android.Content.PM;
using Android.OS;

namespace XamarinFormsChangeAndoirdThemeRuntime.Droid
{
    [Activity(Label = "XamarinFormsChangeAndoirdThemeRuntime", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);

            GlobalVariables.MainThemeResourceID = Resource.Style.MainTheme;
            GlobalVariables.MainTransparentThemeResourceID = Resource.Style.MainTransparentTheme_Base;
            GlobalEvents.OnAndroidThemeChangeNeeded += GlobalEvents_OnAndroidThemeChangeNeeded; ;

            LoadApplication(new App());
        }

        private void GlobalEvents_OnAndroidThemeChangeNeeded(object sender, int themeid)
        {
            RunOnUiThread(() => {
                SetTheme(themeid);
            });
        }
    }
}

As you see, we gain the Theme’s Resource ID to the SetTheme(themeid) function. So we have to store theese Themes, in a global variable. Let’s create a class in the .NET Standard/PCL Project, and define static int variables as the Theme’s IDs (https://github.com/officialdoniald/Xamarin.Forms—Change-Android-Theme-at-runtime/blob/master/XamarinFormsChangeAndoirdThemeRuntime/XamarinFormsChangeAndoirdThemeRuntime/GlobalVariables.cs):

namespace XamarinFormsChangeAndoirdThemeRuntime
{
    public class GlobalVariables
    {
        public static int MainThemeResourceID { get; set; }

        public static int MainTransparentThemeResourceID { get; set; }
    }
}

And now we have to just try theese codes. Let’s create a Page with two Buttons, and subscribe on their Click events:

https://github.com/officialdoniald/Xamarin.Forms—Change-Android-Theme-at-runtime/blob/master/XamarinFormsChangeAndoirdThemeRuntime/XamarinFormsChangeAndoirdThemeRuntime/MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamarinFormsChangeAndoirdThemeRuntime.MainPage">
    <StackLayout>
        <Button x:Name="mainThemeButton" 
                Clicked="MainThemeButton_Clicked" 
                Text="MainTheme"/>
        <Button x:Name="mainTransparentButton" 
                Clicked="MainTransparentButton_Clicked" 
                Text="TransparentTheme"/>
    </StackLayout>
</ContentPage>

https://github.com/officialdoniald/Xamarin.Forms—Change-Android-Theme-at-runtime/blob/master/XamarinFormsChangeAndoirdThemeRuntime/XamarinFormsChangeAndoirdThemeRuntime/MainPage.xaml.cs

using System;
using Xamarin.Forms;

namespace XamarinFormsChangeAndoirdThemeRuntime
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void MainThemeButton_Clicked(object sender, EventArgs e)
        {
            Device.BeginInvokeOnMainThread(() => 
            {
                GlobalEvents.OnAndroidThemeChangeNeeded_Event(null, GlobalVariables.MainThemeResourceID);
            });
        }

        private void MainTransparentButton_Clicked(object sender, EventArgs e)
        {
            Device.BeginInvokeOnMainThread(() =>
            {
                GlobalEvents.OnAndroidThemeChangeNeeded_Event(null, GlobalVariables.MainTransparentThemeResourceID);
            });
        }
    }
}

I defined two Themes, theese Themes change a listview’s color. You can find they at: https://github.com/officialdoniald/Xamarin.Forms—Change-Android-Theme-at-runtime/tree/master/XamarinFormsChangeAndoirdThemeRuntime/XamarinFormsChangeAndoirdThemeRuntime.Android/Resources/values

Their was defined at the color.xml(link above).

Of cource you don’t have to do it this way, that was an extreme way, you can implement in the Android code, but if you want to use it from the .NET Standard/PCL Project, you have to write an event, or write through dependency service.

Fun fact: this GitHub project’s name is too large, so you can’t Debug it without renaming.

Xamarin.Android.SerialPort

This project you can find at: https://github.com/officialdoniald/Xamarin.Android.SerialPort.

Serial Port wrapper for Xamarin.Android.

If you want to change some feature of the Serial Port, just clone this repo and change it. In the SerialPort project you have to go to the SerialPortWrapper folder and the SerialPort.cs.

After the update you have to Rebuild, and if you want to use the recently updatet SerialPort class, you have to add the dll (from bin/Release or Debug) to the target Project.

If you want to create your own SerialPort wrapper just go to https://github.com/chzhong/serial-android and open this project to Android Studio. Create .aar file with grandle: go to grandle tab on the right of the Android Studio (:libserial/build/), select the package and search for clean (double click) and search for assembleRelease (double click). The .aar file youe can find at the project folder and then the \libserial\build\outputs\aar. Then create a Bining Xamarin Android Library, with the grandle (what you gave in the grandle) minimum adroid version, and add it to the Jar folder. (https://developer.xamarin.com/guides/android/advanced_topics/binding-a-java-library/binding-a-jar/ or https://developer.xamarin.com/guides/android/advanced_topics/binding-a-java-library/binding-an-aar/) Rebuild. And that’s it. Watch out for the namespaces. The namespace will be the java’s namespace with upper case at the start.

Xamarin.Android.CanBus

This project you can find at: https://github.com/officialdoniald/Xamarin.Android.Canbus.

Usage:

Don’t forget to copy the lib folder to your project! First step: copy the lib folder to you project. You have to change the Properties of theese files:

libcanbus.so: AndroidNativeLibrary

canbus_helper.jar: AndroidJavaLibrary

canbus_micro_fixed.jar: AndroidJavaLibrary

Create Canbus:

Canbus _canbus = _canbus = new Canbus(250000); _canbus.PackageReceive += Canbus_PackageReceive; _canbus.CanbusStatusChanged += Canbus_CanbusStatusChanged; _canbus.CanbusExceptionOccour += Canbus_CanbusExceptionOccour;

Read from the Canbus:

private void Canbus_PackageReceive(object sender, CanbusEventArgs e) { RunOnUiThread(() => { string dataString = ""; for (int x = 0; x < e.Data.Count; x++) dataString += e.Data[x].ToString("X2") + " ";   
 var text = "ID: " + e.ID.ToString("X2") + ", Ext: " + e.Extended.ToString() + ", Data: " + dataString.Substring(0, dataString.Length - 1);

    _logger.Info(text);
    _label.Text = text;
});
}

Sending to the Canbus:

  _canbus.Send(0x18FF12, true, new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }); 

More information and Example: Xamarin.Android.Canbus.PoC Project: https://github.com/officialdoniald/Xamarin.Android.Canbus/tree/master/Xamarin.Android.Canbus.PoC .

Xamarin.Forms – CustomMasterDetailPage – change navigation icon(back button and “hamburger” menu button) – Android

This project, you can find at: https://github.com/officialdoniald/Xamarin.Forms.CustomControls

We have to create some platforms specific code (custom NavigationPage Renderer), because in the base Xamarin.Forms code, we can’t find this implementation. So first step, we have to create a class in the .NET Standard/PCL project: CustomMasterDetailPage( https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls/MasterDetailPage/CustomMasterDetailPage.cs ).

using Xamarin.Forms; 
using XamarinForms.CustomControls.Enums;
namespace XamarinForms.CustomControls.MasterDetailPage
{
public class CustomMasterDetailPage : Xamarin.Forms.MasterDetailPage
{
public CustomMasterDetailPage() : base() { }
}
}

In Xamarin.Android, we can say that: ok, let’s change the back button and the hamburger menu button image forever, so we have to write an algorythm, that will change our icons. We have to watch the NavigationStack, and it’s count is equal to 1, we have to change the icon to a hamburger menu icon, otherwise we have to change the icon to a back button icon. Let’s do it.

So we have to write a MasterDetailPageRenderer class in the Android Project and we have to write the algorythm there.

 using Android.Content;
 using Android.Graphics;
 using Android.Graphics.Drawables;
 using Android.Runtime;
 using Android.Widget;
 using Plugin.CurrentActivity;
 using System;
 using Xamarin.Forms;
 using Xamarin.Forms.Platform.Android.AppCompat;
 using XamarinForms.CustomControls.Droid.CustomRenderer;
 using XamarinForms.CustomControls.MasterDetailPage;

[assembly: ExportRenderer(typeof(CustomMasterDetailPage), typeof(MasterNavigationPageRenderer))]
 namespace XamarinForms.CustomControls.Droid.CustomRenderer
 { 
pragma warning disable CS0618
public class MasterNavigationPageRenderer : MasterDetailPageRenderer
{
    private static Android.Support.V7.Widget.Toolbar GetToolbar() => (CrossCurrentActivity.Current?.Activity as MainActivity)?.FindViewById(Resource.Id.toolbar);

    private Android.Support.V7.Widget.Toolbar toolbar;

    public MasterNavigationPageRenderer() : base() { }

    public MasterNavigationPageRenderer(Context context) : base(context) { }

    public MasterNavigationPageRenderer(IntPtr a, JniHandleOwnership b) : base() { }

    protected override void OnLayout(bool changed, int l, int t, int r, int b)
    {
        base.OnLayout(changed, l, t, r, b);
        toolbar = GetToolbar();

        if (toolbar != null)
        {
            if (GlobalVariables.NavigationStackCount == 1)
            {
                SetNavigationButton(Resource.Drawable.menu);
            }
            else
            {
                SetNavigationButton(Resource.Drawable.back);
            }
        }
    }

    private void SetNavigationButton(int resourceID)
    {
        var icon = Forms.Context.GetDrawable(resourceID);
        using (var drawable = ((BitmapDrawable)icon).Bitmap)
        using (var bitmap = Bitmap.CreateScaledBitmap(drawable, 80, 80, false))
        using (var newDrawable = new BitmapDrawable(Resources, bitmap))
        {
            toolbar.NavigationIcon = newDrawable;
        }
    }
}
pragma warning restore CS0618
}
GlobalVariables.NavigationStackCount is public static variable. You can defined it in the .NET Standard/PCL project.

Next step, we have to create a platform specific NavigationPageRenderer. So let’s create a class in the .NET Standard/PCL project, and call it CustomNavigationPage ( https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls/NavigationPage/CustomNavigationPage.cs )

Than we have to create the platform spec. implementation in the Android project (https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.Android/CustomRenderer/CustonNavigationPageRenderer.cs) :

 using System;
 using System.ComponentModel;
 using System.Threading.Tasks;
 using Android.Content;
 using Android.Graphics;
 using Android.Graphics.Drawables;
 using Android.Runtime;
 using Android.Support.V7.Graphics.Drawable;
 using Android.Util;
 using Android.Widget;
 using Plugin.CurrentActivity;
 using Xamarin.Forms;
 using Xamarin.Forms.Platform.Android;
 using XamarinForms.CustomControls.Droid.CustomRenderer;
 using XamarinForms.CustomControls.Enums;
 using XamarinForms.CustomControls.NavigationPage;
[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustonNavigationPageRenderer))] 
namespace XamarinForms.CustomControls.Droid.CustomRenderer
 { 
pragma warning disable CS0618
public class CustonNavigationPageRenderer : Xamarin.Forms.Platform.Android.AppCompat.NavigationPageRenderer
{
    #region Properties

    private static Android.Support.V7.Widget.Toolbar GetToolbar() => (CrossCurrentActivity.Current?.Activity as MainActivity)?.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);

    private Android.Support.V7.Widget.Toolbar toolbar;

    private Page view;

    #endregion

    #region Kontruktor

    public CustonNavigationPageRenderer() : base() { }

    public CustonNavigationPageRenderer(Context context) : base(context) { }

    public CustonNavigationPageRenderer(IntPtr a, JniHandleOwnership b) : base() { }

    #endregion

 protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        var page = (CustomNavigationPage)sender;
    }

    protected override void OnLayout(bool changed, int l, int t, int r, int b)
    {
        base.OnLayout(changed, l, t, r, b);

        //You can define special pages, where you can change the rule.
        //if (view is LoginPage || view is PasswordResetPage || view is RegisterPage || view is SMSPage)
        //{
        //    SetIcon();
        //}
    }

    protected override Task<bool> OnPushAsync(Page view, bool animated)
    {
        var retVal = base.OnPushAsync(view, animated);

        GlobalVariables.NavigationStackCount = view.Navigation.NavigationStack.Count;

        this.view = view;

        return retVal;
    }

    protected override Task<bool> OnPopToRootAsync(Page page, bool animated)
    {
        var retVal = base.OnPopToRootAsync(page, animated);

        GlobalVariables.NavigationStackCount = page.Navigation.NavigationStack.Count - 1;

        view = page;

        return retVal;
    }

    protected override Task<bool> OnPopViewAsync(Page page, bool animated)
    {
        var retVal = base.OnPopViewAsync(page, animated);

        GlobalVariables.NavigationStackCount = page.Navigation.NavigationStack.Count - 1;

        view = page;

        return retVal;
    }

    private void SetIcon()
    {
        toolbar = GetToolbar();

        if (toolbar != null)
        {
            if (GlobalVariables.NavigationStackCount == 1)
            {
                SetNavigationButton(Resource.Drawable.menu);
            }
            else
            {
                SetNavigationButton(Resource.Drawable.back);
            }
        }
    }

    private void SetNavigationButton(int resourceID)
    {
        var icon = Forms.Context.GetDrawable(resourceID);
        using (var drawable = ((BitmapDrawable)icon).Bitmap)
        using (var bitmap = Bitmap.CreateScaledBitmap(drawable, 80, 80, false))
        using (var newDrawable = new BitmapDrawable(Resources, bitmap))
        {
            toolbar.NavigationIcon = newDrawable;
        }
    }
}
pragma warning restore CS0618
}

Here the most important thing is to store the GlobalVariables.NavigationStackCount. Here can change the icons too.

Xamarin.Forms – change cursor’s color – Android

This code (https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.Android/CustomRenderer/CustomEntryRenderer.cs ) :

IntPtr IntPtrtextViewClass = JNIEnv.FindClass(typeof(TextView));

IntPtr mCursorDrawableResProperty = JNIEnv.GetFieldID(IntPtrtextViewClass, “mCursorDrawableRes”, “I”);

will change the cursor’s color. If you want to really change the color of the cursor, you have to create in the Resources/Drawable folder a my_cursor.xml file: https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.Android/Resources/drawable/my_cursor.xml

<shape xmlns:android=”http://schemas.android.com/apk/res/android” android:shape=”rectangle”>
<solid android:color=”@color/colorCursors”></solid>
<size android:width=”2dp” />
</shape>

@color/colorCursors is a color from the Resources/values/color.xml. If you don’t have this file, just create it: https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.Android/Resources/values/colors.xml

<?xml version=”1.0″ encoding=”utf-8″?>
<resources>
<color name=”launcher_background”>#FFFFFF</color>
<color name=”colorPrimary”>#3F51B5</color>
<color name=”colorPrimaryDark”>#303F9F</color>
<color name=”colorAccent”>#FF4081</color>
<color name=”colorCursors”>#000000</color>
</resources>

<color name=”colorCursors”>#000000</color> here can you gain the color of the cursor.

Xamarin.Forms – CustomSearchBar

This project, you can find at: https://github.com/officialdoniald/Xamarin.Forms.CustomControls

We have to create some platforms specific code (custom NavigationPage Renderer), because in the base Xamarin.Forms code, we can’t find this implementation. So first step, we have to create a class in the .NET Standard/PCL project: CustomSearchBar ( https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls/SearchBar/CustomSearchBar.cs ).

using Xamarin.Forms;
namespace XamarinForms.CustomControls.SearchBar
{
public class CustomSearchBar : Xamarin.Forms.SearchBar
{
public static readonly BindableProperty BorderColorProperty =
BindableProperty.Create(nameof(BorderColor), typeof(Color), typeof(CustomSearchBar));
public static readonly BindableProperty BorderWidthProperty =
BindableProperty.Create(nameof(BorderWidth), typeof(int), typeof(CustomSearchBar));
/// <summary>
/// Set the Border Color of the SearchBar.
/// </summary>
public Color BorderColor
{
set { SetValue(BorderColorProperty, value); }
get { return (Color)GetValue(BorderColorProperty); }
}
/// <summary>
/// Set the Border Width of the SearchBar.
/// </summary>
public int BorderWidth
{
set { SetValue(BorderWidthProperty, value); }
get { return (int)GetValue(BorderWidthProperty); }
}
}
}

Now, we have to implement to the various platforms. Let’s try with the Android part: https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.Android/CustomRenderer/CustomSearchBarRenderer.cs

With this code, you can change the search icon of the searchbar:

int searchIconId = Context.Resources.GetIdentifier(“android:id/search_mag_icon”, null, null);
var icon = searchView.FindViewById(searchIconId);
(icon as ImageView).SetImageResource(Resource.Drawable.searchbaricon);

You have to create an image in the Resources/Drawable folder with this name: searchbaricon.png (you can change it, but don’t forget to rename Resource.Drawable.your_drawable).

iOS part:

using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using XamarinForms.CustomControls.iOS.CustomRenderer;
using XamarinForms.CustomControls.SearchBar;
[assembly: ExportRenderer(typeof(CustomSearchBar), typeof(CustomSearchBarRenderer))]
namespace XamarinForms.CustomControls.iOS.CustomRenderer
{
public class CustomSearchBarRenderer : SearchBarRenderer
{
#region Properties
private UIColor BorderColor = UIColor.Black;
private int BorderWidth = 1;
#endregion
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.SearchBar> e)
{
base.OnElementChanged(e);
var newElement = ((CustomSearchBar)e.NewElement);
BorderColor = newElement.BorderColor.ToUIColor();
if (newElement.BorderWidth != 0)
{
BorderWidth = newElement.BorderWidth;
}
var searchbar = (UISearchBar)Control;
if (e.NewElement != null)
{
//Foundation.NSString _searchField = new Foundation.NSString(“searchField”);
//var textFieldInsideSearchBar = (UITextField)searchbar.ValueForKey(_searchField);
//textFieldInsideSearchBar.BackgroundColor = UIColor.FromRGB(0, 0, 12);
//textFieldInsideSearchBar.TextColor = UIColor.White;
// searchbar.Layer.BackgroundColor = UIColor.Blue.CGColor;
//searchbar.TintColor = UIColor.White;
//searchbar.BarTintColor = UIColor.White;
searchbar.Layer.CornerRadius = 0;
searchbar.Layer.BorderWidth = BorderWidth;
searchbar.Layer.BorderColor = BorderColor.CGColor;
//searchbar.ShowsCancelButton = false;
}
}
}
}

Xamarin.Forms – CustomEntry (BorderWidth, BorderColor and TextPadding)

This project, you can find at: https://github.com/officialdoniald/Xamarin.Forms.CustomControls

We have to create some platforms specific code (custom NavigationPage Renderer), because in the base Xamarin.Forms code, we can’t find this implementation. So first step, we have to create a class in the .NET Standard/PCL project: CustomEntry ( https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls/Entry/CustomEntry.cs ).

using Xamarin.Forms;
namespace XamarinForms.CustomControls.Entry
{
public class CustomEntry : Xamarin.Forms.Entry
{
public static readonly BindableProperty BorderColorProperty =
BindableProperty.Create(nameof(BorderColor), typeof(Color), typeof(CustomEntry));
public static readonly BindableProperty BorderWidthProperty =
BindableProperty.Create(nameof(BorderWidth), typeof(int), typeof(CustomEntry));
public static readonly BindableProperty TextPaddingProperty =
BindableProperty.Create(nameof(TextPadding), typeof(Thickness), typeof(CustomEntry));
/// <summary>
/// Set the Border Color of the Entry.
/// </summary>
public Color BorderColor
{
set { SetValue(BorderColorProperty, value); }
get { return (Color)GetValue(BorderColorProperty); }
}
/// <summary>
/// Set the Border Width of the Entry.
/// </summary>
public int BorderWidth
{
set { SetValue(BorderWidthProperty, value); }
get { return (int)GetValue(BorderWidthProperty); }
}
/// <summary>
/// Set the Text Padding of the Entry.
/// </summary>
public Thickness TextPadding
{
set { SetValue(TextPaddingProperty, value); }
get { return (Thickness)GetValue(TextPaddingProperty); }
}
}
}

Now, we have to implement to the various platforms. Let’s begin with Android: https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.Android/CustomRenderer/CustomEntryRenderer.cs

using System;
using System.ComponentModel;
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using XamarinForms.CustomControls.Droid.CustomRenderer;
using XamarinForms.CustomControls.Entry;
[assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))]
namespace XamarinForms.CustomControls.Droid.CustomRenderer
{
#pragma warning disable CS0618
public class CustomEntryRenderer : EntryRenderer
{
private Android.Graphics.Color BorderColor = Android.Graphics.Color.Black;
private int BorderWidth = 1;
private Thickness TextPadding = new Thickness(0);
private CustomEntry CustomEntry;
#endregion
#region Konstruktor
public CustomEntryRenderer() : base() { }
public CustomEntryRenderer(Context context) : base(context) { }
public CustomEntryRenderer(IntPtr a, JniHandleOwnership b) : base() { }
#endregion
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
BorderColor = CustomEntry.BorderColor.ToAndroid();
if (CustomEntry.BorderWidth != 0)
{
BorderWidth = CustomEntry.BorderWidth;
}
TextPadding = CustomEntry.TextPadding;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Entry> e)
{
base.OnElementChanged(e);
CustomEntry = e.NewElement as CustomEntry;
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
if (CustomEntry != null)
{
ChangeCustomEntryProperties(CustomEntry);
Control.Gravity = GravityFlags.CenterVertical;
int left = Convert.ToInt32(TextPadding.Left);
int right = Convert.ToInt32(TextPadding.Right);
int top = Convert.ToInt32(TextPadding.Top);
int bottom = Convert.ToInt32(TextPadding.Bottom);
Control.SetPadding(left, top, right, bottom);
IntPtr IntPtrtextViewClass = JNIEnv.FindClass(typeof(TextView));
IntPtr mCursorDrawableResProperty = JNIEnv.GetFieldID(IntPtrtextViewClass, “mCursorDrawableRes”, “I”);
JNIEnv.SetField(Control.Handle, mCursorDrawableResProperty, Resource.Drawable.my_cursor);
}
}
private void ChangeCustomEntryProperties(CustomEntry entry)
{
var customEntry = entry;
var nativeEditText = (EditText)Control;
var shape = new ShapeDrawable(new Android.Graphics.Drawables.Shapes.RectShape());
shape.Paint.Color = BorderColor;
shape.Paint.SetStyle(Paint.Style.Stroke);
nativeEditText.Background = shape;
GradientDrawable gd = new GradientDrawable();
gd.SetColor(Android.Graphics.Color.White);
gd.SetStroke(BorderWidth, BorderColor);
nativeEditText.SetBackground(gd);
}
}
#pragma warning restore CS0618
}

This code:

IntPtr IntPtrtextViewClass = JNIEnv.FindClass(typeof(TextView));

IntPtr mCursorDrawableResProperty = JNIEnv.GetFieldID(IntPtrtextViewClass, “mCursorDrawableRes”, “I”);

will change the cursor’s color. If you want to really change the color of the cursor, you have to create in the Resources/Drawable folder a my_cursor.xml file:

<shape xmlns:android=”http://schemas.android.com/apk/res/android” android:shape=”rectangle”>
<solid android:color=”@color/colorCursors”></solid>
<size android:width=”2dp” />
</shape>

@color/colorCursors is a color from the Resources/values/color.xml. If you don’t have this file, just create it:

<?xml version=”1.0″ encoding=”utf-8″?>
<resources>
<color name=”launcher_background”>#FFFFFF</color>
<color name=”colorPrimary”>#3F51B5</color>
<color name=”colorPrimaryDark”>#303F9F</color>
<color name=”colorAccent”>#FF4081</color>
<color name=”colorCursors”>#000000</color>
</resources>

<color name=”colorCursors”>#000000</color> here can you gain the color of the cursor.

If you don’t want to change, just skip this code, comment in it.

Now continue with the iOS site: https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.iOS/CustomRenderer/CustomEntryRenderer.cs

using System.ComponentModel;
using CoreGraphics;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using XamarinForms.CustomControls.Entry;
using XamarinForms.CustomControls.iOS.CustomRenderer;
[assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))]
namespace XamarinForms.CustomControls.iOS.CustomRenderer
{
public class CustomEntryRenderer : EntryRenderer
{
#region Properties
private UIColor BorderColor = UIColor.Black;
private int BorderWidth = 1;
private Thickness TextPadding = new Thickness(0);
#endregion
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var element = ((CustomEntry)sender);
BorderColor = element.BorderColor.ToUIColor();
BorderWidth = element.BorderWidth;
TextPadding = element.TextPadding;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.Layer.BorderWidth = BorderWidth;
Control.VerticalAlignment = UIControlContentVerticalAlignment.Center;
Control.LeftView = new UIView(new CGRect(TextPadding.Left, TextPadding.Top, TextPadding.Right, TextPadding.Bottom));
Control.LeftViewMode = UITextFieldViewMode.Always;
Control.TintColor = UIColor.Black;
Control.Layer.BorderColor = BorderColor.CGColor;
//Control.RightView = new UIView(new CGRect(0, 0, 15, 0));
//Control.RightViewMode = UITextFieldViewMode.Always;
//// Use tint color to change the cursor’s color
//Control.TintColor = UIColor.White;
}
}
}
}