Xamarin

O Xamarin usa um sistema de bibliotecas de associação (binding libraries) que nos permite integrar bibliotecas nativas, estejam elas disponíveis em código Kotlin ou Java no Android ou em código Swift ou Objective-C no iOS.

Este guia ensina como integrar o AllowMeSDK ao seu aplicativo Xamarin.

📘

Importante!

O tutorial abaixo possui simplificações por se tratar do código de um app de exemplo. Os resultados das chamadas são mostrados na tela para facilitar o entendimento. Você deverá fazer as modificações necessárias para a sua implementação real. Além disso, o código completo de exemplo está disponível. Entre em contato com [email protected] para ter acesso.

1. Implementação Nativa Android

Antes de prosseguir nessa seção é recomendado a leitura da integração nativa para Android. Além disso, também é fortemente recomendado a leitura da documentação oficial para criação de Kotlin Binding Library no Xamarin (opens new window). O tutorial abaixo foi baseado no descrito na documentação oficial!

1.1. Obtendo acesso ao AllowMeSDK no formato aar

O AllowMeSDK é disponibilizado para integração via Gradle, o sistema padrão usado em bibliotecas Android. Por se tratar de um aplicativo em Xamarin, que não oferece suporte ao Gradle, o primeiro passo será ter acesso ao arquivo aar (opens new window), que é o formato de distribuição do binário de uma biblioteca Android.

Para isso, observe a versão mais recente do AllowMeSDK no nosso portal de Release Notes (opens new window)e modifique as URL abaixo para acessá-las:

https://android.allowmecloud.com/sdk/br/com/allowme/android/allowme-sdk/<VERSION>/allowme-sdk-<VERSION>.aar

Como exemplo, vamos considerar a versão 3.2.0 como a desejada. As URLs serão

// Para o SDK de homologação
https://android.allowmecloud.com/sdk/br/com/allowme/android/allowme-sdk/3.2.0-stage/allowme-sdk-3.2.0-stage.aar

// Para o SDK de produção
https://android.allowmecloud.com/sdk/br/com/allowme/android/allowme-sdk/3.2.0/allowme-sdk-3.2.0.aar

Acesse os links pelo seu navegador e digite as credenciais que lhe foram passadas pelo nosso time. O download dos aar's deve começar automaticamente.

1.2. Criando uma biblioteca de associação (binding library)

Com os aar's em mãos, abra o Visual Studio e crie um projeto de uma Android Binding Library como mostrado abaixo

O passo a passo para fazer isso é New File → New Project → Android → Library → Bindings Library. De um nome, escolha uma localização e crie o projeto.

Com o projeto criado, adicione o arquivo aar obtido na etapa anterior dentro da pasta Jars do projeto recém-criado.

📘

Importante!

Por causa de uma limitação do Xamarin.Android, uma biblioteca de associação deve conter apenas UM arquivo aar. Você deverá repetir os passos para criação da biblioteca de associação duas vezes: uma para o AllowMeSDK Homologação e outra para o de Produção conforme descrito na documentação oficial

1.3. Adicionando as dependências na biblioteca de associação via Nuget

Para criar uma biblioteca de associação com todas as classes disponíveis e funcionando corretamente, é necessário adicionar as dependências do SDK na biblioteca de associação. Todas as dependências necessárias estão disponíveis via Nuget. As dependências usadas atualmente no nosso SDK Android estão listadas abaixo e devem ser adicionadas à sua biblioteca de associação:

<PackageReference Include="Xamarin.Kotlin.StdLib">
  <Version>1.9.0.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.KotlinX.Coroutines.Core">
  <Version>1.7.3</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.Core">
  <Version>1.10.1.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.GooglePlayServices.Tasks">
  <Version>118.0.2.3</Version>
</PackageReference>
<PackageReference Include="Xamarin.GooglePlayServices.Ads.Identifier">
  <Version>118.0.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.GooglePlayServices.AppSet">
  <Version>16.0.2</Version>
</PackageReference>
<PackageReference Include="RootBeer">
  <Version>0.0.8.2</Version>
</PackageReference>
<PackageReference Include="Square.OkHttp3">
  <Version>4.11.0.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.Room.Runtime">
  <Version>2.5.2.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.Work.Runtime">
  <Version>2.8.1.3</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.ConstraintLayout">
  <Version>2.1.4.6</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.Camera.Camera2">
  <Version>1.2.3.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.Camera.Lifecycle">
  <Version>1.2.3.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.Camera.View">
  <Version>1.2.3.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.Google.MLKit.FaceDetection">
  <Version>116.1.5.5</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.Lifecycle.Process">
  <Version>2.6.1.3</Version>
</PackageReference>
<PackageReference Include="Com.Airbnb.Android.Lottie">
  <Version>4.2.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.DataBinding.ViewBinding">
  <Version>8.1.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.KotlinX.Coroutines.Android">
  <Version>1.7.3</Version>
</PackageReference>
<PackageReference Include="Xamarin.KotlinX.Coroutines.Core.Jvm">
  <Version>1.7.3</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.AppCompat">
  <Version>1.6.1.3</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.Lifecycle.LiveData">
  <Version>2.6.1.1</Version>
</PackageReference>

📘

Fique atento nas dependências!

As dependências do projeto podem mudar dependendo da versão utilizada 😔 Caso encontre algum erro nesse sentido, verifique as dependências da versão que você está usando acessando o link https://android.allowmecloud.com/sdk/br/com/allowme/android/allowme-sdk/<VERSION>/allowme-sdk-<VERSION>.pom e fazendo o download do arquivo .pom. Você pode abrí-lo num editor de textos e acessar a lista de dependências do nosso SDK para aquela versão!

1.4. Compilando a biblioteca de associação

Ao tentar buildar o projeto você pode observar alguns erros surgirem. Se isso acontecer, ignore as classes que são mostradas nos erros dentro do arquivo Transforms/Metadata.xml usando o código abaixo:

<remove-node path="/api/package[starts-with(@name,'NOME_DA_CLASSE')]" />

Se nenhum erro aconteceu, siga em frente! Acesse o arquivo .dll gerado dentro da pasta bin do projeto da biblioteca de associação.

1.5. Adicionando o dll ao seu app

Com o arquivo dll em mãos, abra o projeto do seu app no Visual Studio, vá até o módulo NOME-PROJETO.Android, clique com o botão direito na pasta References → Add Project Reference. Procure a o arquivo dll nas suas pastas e adicione-o. Ele será definido como um .NET Assembly.

Depois de adicionar o dll gerado você deve adicionar as dependências do AllowMeSDK ao seu projeto exatamente como foi descrito anteriormente na seção Adicionando as dependências na biblioteca de associação via Nuget.

📘

Dica

Fique atento para as versões das dependências: adicionar a versão errada pode causar conflitos e resultar no mau funcionamento do SDK.

1.7. Inicializando o AllowMeSDK

Essa seção se refere à implementação em Xamarin do que é mostrado com mais detalhes em Primeiros Passos - Inicializar o SDK da documentação nativa.No seu módulo NOME-PROJETO.Android, crie uma classe para executar o AllowMe e adicione um método como o mostrado abaixo para passar a sua API key e chamar o método setup. O retorno do método setup deve ser feito através da interface ISetupCallback que implementam as funções ISetupCallback.Error(Throwable throwable) e ISetupCallback.Success().

using BR.Com.Allowme.Android.Allowmesdk;
{...}

namespace AllowMeNative.Droid
{
    public class AllowMeNative : Java.Lang.Object, ISetupCallback
    {
        AllowMe allowMe;
        Context context = Android.App.Application.Context;
        Action<string> action = null;

        public void apiKey(string value, Action<string> completion)
        {
            allowMe = AllowMe.GetInstance(context, value);

            action = completion;
            allowMe.Setup(this);
        }

        void ISetupCallback.Error(Throwable throwable)
        {
            if (action != null)
            {
                action.Invoke(throwable.ToString());
            }
        }

        void ISetupCallback.Success()
        {
            if (action != null)
            {
                action.Invoke("Setup Success");
            }
        }
    }
}

1.8. Implementando o Device Intelligence (Contextual)

Essa seção se refere à implementação em Xamarin do que é mostrado com mais detalhes em Device Intelligence (Contextual) da documentação nativa.Para implementar o método collect, adicione à classe criada anteriormente uma chamada para o método do AllowMeSDK. Também será necessária a implementação da interface ICollectCallback com as funções ICollectCallback.Error(Throwable throwable) e ICollectCallback.Success(string collect) para retorno do resultado.

using BR.Com.Allowme.Android.Allowmesdk;
{...}

namespace AllowMeNative.Droid
{
    public class AllowMeNative : Java.Lang.Object, ICollectCallback
    {
        {...}
        Action<string, string> action = null;

        public void Collect(Action<string, string> completion)
        {
            action = completion;            
            allowMe.Collect(this);
        }

        void ICollectCallback.Error(Throwable throwable)
        {
            Console.WriteLine(throwable.ToString());
            if (action != null)
            {
                action.Invoke(null, throwable.ToString());
            }
        }

        void ICollectCallback.Success(string collect)
        {
            if (action != null)
            {
                action.Invoke(collect, null);
            }
        }
    }
}

1.9. Implementando a Biometria Facial

Essa seção se refere à implementação em Xamarin do que é mostrado com mais detalhes em Biometria Facial da documentação nativa.Para implementar a biometria facial no Xamarin, observe a chamada da função abaixo:

using BR.Com.Allowme.Android.Allowmesdk;
using BR.Com.Allowme.Android.Allowmesdk.Biometry.View;
{...}

namespace AllowMeNative.Droid
{
    public class AllowMeNative : Java.Lang.Object, ICollectCallback
    {
        {...}
        public void StartBiometrics(Action<string> completion)
        {
            Intent intent = new Intent(context, typeof(AllowMeBiometryActivity));
            var activity = Forms.Context as Activity;

            activity.StartActivityForResult(intent, 0);

            MessagingCenter.Subscribe<System.Object, string>(this, "SendNotificationToAllowMe", async (sender, arg) =>
            {
                completion.Invoke(arg);
            });
        }
    }
}

📘

Dica

Observe a documentação nativa para verificar as possibilidades de customização da tela da Biometria Facial.

Nesse caso, o resultado da activity da biometria facial é retornado para a classe MainActivity.cs por meio da chamada da activity pela função StartActivityForResult. Assim, a MainActivity deverá implementar a função OnActivityResult para receber o resultado da StartActivityForResult.

{...}

namespace AllowMeExample.Droid
{
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        {...}

        protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
        {
            if (requestCode == 0)
            {
                if (data != null)
                {
                    var biometryResult = data.Extras.GetParcelable(AllowMeBiometryActivity.BiometryResultKey) as BiometryResult;

                    if (resultCode == Result.Ok)
                    {

                        String result = // process result

                        MessagingCenter.Send<Object, string>(this, "SendNotificationToAllowMe", result);
                    }
                    else
                    {
                        MessagingCenter.Send<Object, string>(this, "SendNotificationToAllowMe", biometryResult.ErrorType.ToString());
                    }
                } else
                {
                    MessagingCenter.Send<Object, string>(this, "SendNotificationToAllowMe", "Sem resultado");
                }
            }
        }
    }
}

Observe que escolhemos o MessagingCenter (opens new window)para lidar com a comunicação entre a nossa classe AllowMeNative e a MainActivity do Android. Dentro da classe AllowMeNative usamos o método MessagingCenter.Subscribe para nos increver no centro de mensagens e monitorar o recebimento do resultado. Quando recebemos o resultado na MainActivity, enviamos ele para o nosso inscrito por meio do método MessagingCenter.Send. Isso nos permite manter o padrão para retorno do resultado por meio de Actions como visto nos outros casos. Apesar de termos usado o MessagingCenter, essa implementação poderá ser feita de outras maneiras, da forma que você achar mais conveniente para o seu aplicativo!

Além disso, o resultado acima é transformado em String para ser mostrado na tela a partir do código em comum Xamarin. Faça as modificações necessárias para a sua implementação!

2. Implementação Nativa iOS

Antes de prosseguir nessa seção é recomendado a leitura da integração nativa para iOS. Além disso, também é fortemente recomendado a leitura da documentação oficial para criação de Swift Binding Library no Xamarin. O tutorial abaixo foi baseado no descrito na documentação oficial!

2.1. Obtendo acesso ao AllowMeSDK no formato XCFramework

O AllowMeSDK é disponibilizado para integração via Cocoapods e Swift Package Manager, os sistemas mais usados em bibliotecas iOS. Entretanto, aplicativos Xamarin não tem suporte a esses gerenciadores de pacotes por isso o primeiro passo será ter acesso ao arquivo XCFramework, que é o formato de distribuição do binário de uma biblioteca iOS.

Para isso, observe a seção Adicionar o framework como dependência | Binário da documentação nativa do iOS.

2.2. Criando uma biblioteca de associação (binding library)

Com os xcframework's em mãos, abra o Visual Studio e crie um projeto de uma iOS Binding Library como mostrado abaixo

Criação de uma Binding Library para iOS

O passo a passo para fazer isso é New File → New Project → iOS → Library → Bindings Library. Dê um nome, escolha uma localização e crie o projeto.

Com o projeto criado, adicione o arquivo xcframework obtido na etapa anterior dentro da pasta Native References do projeto recém-criado. Para isso botão direito do mouse → Add Native Reference.

📘

Importante!

Por causa de uma limitação do Xamarin.iOS, uma biblioteca de associação deve conter apenas UM arquivo xcframework. Você deverá repetir os passos para criação da biblioteca de associação duas vezes: uma para o AllowMeSDK Homologação e outra para o de Produção conforme descrito na documentação oficial

2.3 Configurando o XCFramework na biblioteca de associação

Após adicionar o arquivo a pasta Native References, selecione-o e clique com o botão direito para acessar suas Properties. Será necessário modificar algumas das propriedades descritas ai conforme mostrado na figura e descrito abaixo:

As configurações são:

Force Load → desmarcado
Frameworks → Contacts EventKit AVKit Photos MediaPlayer AppTrackingTransparency SystemConfiguration WebKit Foundation UIKit CoreLocation BackgroundTasks AVFoundation CoreImage LocalAuthentication Vision CoreHaptics AudioToolbox CoreTelephony
GCC Exception Handling → desmarcado
Is C++ → desmarcado
Kind → Framework
Linker Flags → vazio
Smart Link → marcado
Weak Frameworks → vazio

2.4. Modificando ApiDefinition.cs da biblioteca de associação

Para que a biblioteca de associação seja criada com sucesso, será necessário expor as classes públicas do AllowMeSDK por meio do arquivo ApiDefinition.cs. Copie e cole o código abaixo no seu arquivo de definição dentro da sua biblioteca de associação para ter acesso as classes públicas do AllowMeSDK por meio do seu app Xamarin.

using System;
using Foundation;
using ObjCRuntime;
using UIKit;

namespace AllowMeWrapper
{
    // @interface AddressModel : NSObject
    [BaseType(typeof(NSObject), Name = "_TtC17AllowMeSDKHomolog12AddressModel")]
    [DisableDefaultCtor]
    interface AddressModel
    {
        // @property (copy, nonatomic) NSString * _Nonnull street;
        [Export("street")]
        string Street { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull number;
        [Export("number")]
        string Number { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull neighbourhood;
        [Export("neighbourhood")]
        string Neighbourhood { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull city;
        [Export("city")]
        string City { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull state;
        [Export("state")]
        string State { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull zipCode;
        [Export("zipCode")]
        string ZipCode { get; set; }

        // @property (copy, nonatomic) NSString * _Nullable unit;
        [NullAllowed, Export("unit")]
        string Unit { get; set; }

        // @property (copy, nonatomic) NSString * _Nullable country;
        [NullAllowed, Export("country")]
        string Country { get; set; }

        // @property (nonatomic, strong) PinpointModel * _Nullable pinpoint;
        [NullAllowed, Export("pinpoint", ArgumentSemantic.Strong)]
        PinpointModel Pinpoint { get; set; }

        // -(instancetype _Nonnull)initWithStreet:(NSString * _Nonnull)street number:(NSString * _Nonnull)number neighbourhood:(NSString * _Nonnull)neighbourhood city:(NSString * _Nonnull)city state:(NSString * _Nonnull)state zipCode:(NSString * _Nonnull)zipCode __attribute__((objc_designated_initializer));
        [Export("initWithStreet:number:neighbourhood:city:state:zipCode:")]
        [DesignatedInitializer]
        AddressModel Constructor(string street, string number, string neighbourhood, string city, string state, string zipCode);

        // -(instancetype _Nonnull)initWithStreet:(NSString * _Nonnull)street number:(NSString * _Nonnull)number neighbourhood:(NSString * _Nonnull)neighbourhood city:(NSString * _Nonnull)city state:(NSString * _Nonnull)state zipCode:(NSString * _Nonnull)zipCode unit:(NSString * _Nonnull)unit country:(NSString * _Nonnull)country pinpoint:(PinpointModel * _Nonnull)pinpoint __attribute__((objc_designated_initializer));
        [Export("initWithStreet:number:neighbourhood:city:state:zipCode:unit:country:pinpoint:")]
        [DesignatedInitializer]
        AddressModel Constructor(string street, string number, string neighbourhood, string city, string state, string zipCode, string unit, string country, PinpointModel pinpoint);
    }

    // @interface AllowMeBiometryConfig : NSObject
    [BaseType(typeof(NSObject), Name = "_TtC17AllowMeSDKHomolog21AllowMeBiometryConfig")]
    interface AllowMeBiometryConfig
    {
        // @property (nonatomic) NSTimeInterval timeoutInSeconds;
        [Export("timeoutInSeconds")]
        double TimeoutInSeconds { get; set; }

        // @property (nonatomic) BOOL showCancelButton;
        [Export("showCancelButton")]
        bool ShowCancelButton { get; set; }

        // @property (nonatomic) UIModalPresentationStyle presentationStyle;
        [Export("presentationStyle", ArgumentSemantic.Assign)]
        UIModalPresentationStyle PresentationStyle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull screenTitle;
        [Export("screenTitle")]
        string ScreenTitle { get; set; }

        // @property (nonatomic, strong) UIColor * _Nonnull backgroundColor;
        [Export("backgroundColor", ArgumentSemantic.Strong)]
        UIColor BackgroundColor { get; set; }

        // @property (nonatomic, strong) UIColor * _Nonnull overlayBorderColor;
        [Export("overlayBorderColor", ArgumentSemantic.Strong)]
        UIColor OverlayBorderColor { get; set; }

        // @property (nonatomic, strong) AllowMeBiometryTexts * _Nonnull setOfTexts;
        [Export("setOfTexts", ArgumentSemantic.Strong)]
        AllowMeBiometryTexts SetOfTexts { get; set; }

        // @property (nonatomic, strong) UIColor * _Nonnull textColor;
        [Export("textColor", ArgumentSemantic.Strong)]
        UIColor TextColor { get; set; }

        // @property (nonatomic, strong) UIFont * _Nullable titleFont;
        [NullAllowed, Export("titleFont", ArgumentSemantic.Strong)]
        UIFont TitleFont { get; set; }

        // @property (nonatomic, strong) UIFont * _Nullable subtitleFont;
        [NullAllowed, Export("subtitleFont", ArgumentSemantic.Strong)]
        UIFont SubtitleFont { get; set; }

        // @property (nonatomic, strong) UIColor * _Nonnull poweredByColor;
        [Export("poweredByColor", ArgumentSemantic.Strong)]
        UIColor PoweredByColor { get; set; }

        // -(instancetype _Nonnull)initWithTimeoutInSeconds:(NSTimeInterval)timeoutInSeconds showCancelButton:(BOOL)showCancelButton presentationStyle:(UIModalPresentationStyle)presentationStyle screenTitle:(NSString * _Nonnull)screenTitle backgroundColor:(UIColor * _Nonnull)backgroundColor overlayBorderColor:(UIColor * _Nonnull)overlayBorderColor setOfTexts:(AllowMeBiometryTexts * _Nonnull)setOfTexts textColor:(UIColor * _Nonnull)textColor titleFont:(UIFont * _Nullable)titleFont subtitleFont:(UIFont * _Nullable)subtitleFont poweredByColor:(UIColor * _Nonnull)poweredByColor __attribute__((objc_designated_initializer));
        [Export("initWithTimeoutInSeconds:showCancelButton:presentationStyle:screenTitle:backgroundColor:overlayBorderColor:setOfTexts:textColor:titleFont:subtitleFont:poweredByColor:")]
        [DesignatedInitializer]
        AllowMeBiometryConfig Constructor(double timeoutInSeconds, bool showCancelButton, UIModalPresentationStyle presentationStyle, string screenTitle, UIColor backgroundColor, UIColor overlayBorderColor, AllowMeBiometryTexts setOfTexts, UIColor textColor, [NullAllowed] UIFont titleFont, [NullAllowed] UIFont subtitleFont, UIColor poweredByColor);
    }

    // @protocol AllowMeBiometryDelegate
    [Protocol(Name = "_TtP17AllowMeSDKHomolog23AllowMeBiometryDelegate_"), Model(AutoGeneratedName = true)]
    [BaseType(typeof(NSObject))]
    interface AllowMeBiometryDelegate
    {
        // @required -(void)biometryDidFinishWithBiometryObject:(AllowMeBiometryResult * _Nullable)biometryObject error:(NSError * _Nullable)error;
        [Abstract]
        [Export("biometryDidFinishWithBiometryObject:error:")]
        void BiometryDidFinish([NullAllowed] AllowMeBiometryResult biometryObject, [NullAllowed] NSError error);
    }

    // @interface AllowMeBiometryResult : NSObject
    [BaseType(typeof(NSObject), Name = "_TtC17AllowMeSDKHomolog21AllowMeBiometryResult")]
    [DisableDefaultCtor]
    interface AllowMeBiometryResult
    {
        // @property (readonly, copy, nonatomic) NSString * _Nullable payload;
        [NullAllowed, Export("payload")]
        string Payload { get; }

        // @property (readonly, copy, nonatomic) NSArray<NSData *> * _Nonnull images;
        [Export("images", ArgumentSemantic.Copy)]
        NSData[] Images { get; }
    }

    // @interface AllowMeBiometryTexts : NSObject
    [BaseType(typeof(NSObject), Name = "_TtC17AllowMeSDKHomolog20AllowMeBiometryTexts")]
    interface AllowMeBiometryTexts
    {
        // @property (copy, nonatomic) NSString * _Nonnull moveAwayTitle;
        [Export("moveAwayTitle")]
        string MoveAwayTitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull moveAwaySubtitle;
        [Export("moveAwaySubtitle")]
        string MoveAwaySubtitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull getCloserTitle;
        [Export("getCloserTitle")]
        string GetCloserTitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull getCloserSubtitle;
        [Export("getCloserSubtitle")]
        string GetCloserSubtitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull wrongOrientationTitle;
        [Export("wrongOrientationTitle")]
        string WrongOrientationTitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull wrongOrientationSubtitle;
        [Export("wrongOrientationSubtitle")]
        string WrongOrientationSubtitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull notFoundTitle;
        [Export("notFoundTitle")]
        string NotFoundTitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull notFoundSubtitle;
        [Export("notFoundSubtitle")]
        string NotFoundSubtitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull blinkTitle;
        [Export("blinkTitle")]
        string BlinkTitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull blinkSubtitle;
        [Export("blinkSubtitle")]
        string BlinkSubtitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull smileTitle;
        [Export("smileTitle")]
        string SmileTitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull smileSubtitle;
        [Export("smileSubtitle")]
        string SmileSubtitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull delayTitle;
        [Export("delayTitle")]
        string DelayTitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull delaySubtitle;
        [Export("delaySubtitle")]
        string DelaySubtitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull startingViewTitle;
        [Export("startingViewTitle")]
        string StartingViewTitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull startingViewSubtitle;
        [Export("startingViewSubtitle")]
        string StartingViewSubtitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull finalViewTitle;
        [Export("finalViewTitle")]
        string FinalViewTitle { get; set; }

        // @property (copy, nonatomic) NSString * _Nonnull finalViewSubtitle;
        [Export("finalViewSubtitle")]
        string FinalViewSubtitle { get; set; }

        // -(instancetype _Nonnull)initWithMoveAwayTitle:(NSString * _Nonnull)moveAwayTitle moveAwaySubtitle:(NSString * _Nonnull)moveAwaySubtitle getCloserTitle:(NSString * _Nonnull)getCloserTitle getCloserSubtitle:(NSString * _Nonnull)getCloserSubtitle wrongOrientationTitle:(NSString * _Nonnull)wrongOrientationTitle wrongOrientationSubtitle:(NSString * _Nonnull)wrongOrientationSubtitle notFoundTitle:(NSString * _Nonnull)notFoundTitle notFoundSubtitle:(NSString * _Nonnull)notFoundSubtitle blinkTitle:(NSString * _Nonnull)blinkTitle blinkSubtitle:(NSString * _Nonnull)blinkSubtitle smileTitle:(NSString * _Nonnull)smileTitle smileSubtitle:(NSString * _Nonnull)smileSubtitle delayTitle:(NSString * _Nonnull)delayTitle delaySubtitle:(NSString * _Nonnull)delaySubtitle startingViewTitle:(NSString * _Nonnull)startingViewTitle startingViewSubtitle:(NSString * _Nonnull)startingViewSubtitle finalViewTitle:(NSString * _Nonnull)finalViewTitle finalViewSubtitle:(NSString * _Nonnull)finalViewSubtitle __attribute__((objc_designated_initializer));
        [Export("initWithMoveAwayTitle:moveAwaySubtitle:getCloserTitle:getCloserSubtitle:wrongOrientationTitle:wrongOrientationSubtitle:notFoundTitle:notFoundSubtitle:blinkTitle:blinkSubtitle:smileTitle:smileSubtitle:delayTitle:delaySubtitle:startingViewTitle:startingViewSubtitle:finalViewTitle:finalViewSubtitle:")]
        [DesignatedInitializer]
        AllowMeBiometryTexts Constructor(string moveAwayTitle, string moveAwaySubtitle, string getCloserTitle, string getCloserSubtitle, string wrongOrientationTitle, string wrongOrientationSubtitle, string notFoundTitle, string notFoundSubtitle, string blinkTitle, string blinkSubtitle, string smileTitle, string smileSubtitle, string delayTitle, string delaySubtitle, string startingViewTitle, string startingViewSubtitle, string finalViewTitle, string finalViewSubtitle);
    }

    // @interface PersonModel : NSObject
    [BaseType(typeof(NSObject), Name = "_TtC17AllowMeSDKHomolog11PersonModel")]
    [DisableDefaultCtor]
    interface PersonModel
    {
        // @property (readonly, copy, nonatomic) NSString * _Nonnull name;
        [Export("name")]
        string Name { get; }

        // @property (readonly, copy, nonatomic) NSString * _Nonnull nationalId;
        [Export("nationalId")]
        string NationalId { get; }

        // @property (nonatomic, strong) AddressModel * _Nonnull address;
        [Export("address", ArgumentSemantic.Strong)]
        AddressModel Address { get; set; }

        // @property (copy, nonatomic) NSString * _Nullable email;
        [NullAllowed, Export("email")]
        string Email { get; set; }

        // @property (copy, nonatomic) NSString * _Nullable phone;
        [NullAllowed, Export("phone")]
        string Phone { get; set; }

        // -(instancetype _Nonnull)initWithName:(NSString * _Nonnull)name nationalId:(NSString * _Nonnull)nationalId address:(AddressModel * _Nonnull)address __attribute__((objc_designated_initializer));
        [Export("initWithName:nationalId:address:")]
        [DesignatedInitializer]
        PersonModel Constructor(string name, string nationalId, AddressModel address);

        // -(instancetype _Nonnull)initWithName:(NSString * _Nonnull)name nationalId:(NSString * _Nonnull)nationalId address:(AddressModel * _Nonnull)address email:(NSString * _Nonnull)email phone:(NSString * _Nonnull)phone __attribute__((objc_designated_initializer));
        [Export("initWithName:nationalId:address:email:phone:")]
        [DesignatedInitializer]
        PersonModel Constructor(string name, string nationalId, AddressModel address, string email, string phone);
    }

    // @interface PinpointModel : NSObject
    [BaseType(typeof(NSObject), Name = "_TtC17AllowMeSDKHomolog13PinpointModel")]
    [DisableDefaultCtor]
    interface PinpointModel
    {
        // @property (nonatomic) double latitude;
        [Export("latitude")]
        double Latitude { get; set; }

        // @property (nonatomic) double longitude;
        [Export("longitude")]
        double Longitude { get; set; }

        // @property (nonatomic) BOOL isLocationModified;
        [Export("isLocationModified")]
        bool IsLocationModified { get; set; }

        // -(instancetype _Nonnull)initWithLatitude:(double)latitude longitude:(double)longitude isLocationModified:(BOOL)isLocationModified __attribute__((objc_designated_initializer));
        [Export("initWithLatitude:longitude:isLocationModified:")]
        [DesignatedInitializer]
        PinpointModel Constructor(double latitude, double longitude, bool isLocationModified);
    }

    // @interface AllowMe : NSObject
    [BaseType(typeof(NSObject), Name = "_TtC17AllowMeSDKHomolog7AllowMe")]
    [DisableDefaultCtor]
    interface AllowMe
    {
        // +(AllowMe * _Nullable)getInstanceWithApiKey:(NSString * _Nullable)apiKey error:(NSError * _Nullable * _Nullable)error __attribute__((warn_unused_result("")));
        [Static]
        [Export("getInstanceWithApiKey:error:")]
        [return: NullAllowed]
        AllowMe GetInstanceWithApiKey([NullAllowed] string apiKey, [NullAllowed] out NSError error);

        // -(instancetype _Nullable)initWithApiKey:(NSString * _Nonnull)apiKey error:(NSError * _Nullable * _Nullable)error __attribute__((objc_designated_initializer)) __attribute__((deprecated("Use AllowMe.getInstance(withApiKey:_) instead of it.")));
        [Export("initWithApiKey:error:")]
        [DesignatedInitializer]
        AllowMe Constructor(string apiKey, [NullAllowed] out NSError error);

        // -(void)setupWithCompletion:(void (^ _Nonnull)(NSError * _Nullable))completion;
        [Export("setupWithCompletion:")]
        void SetupWithCompletion(Action<NSError> completion);

        // -(NSError * _Nullable)start __attribute__((warn_unused_result("")));
        [Export("start")]
        [return: NullAllowed]
        NSError Start();

        // -(void)collectOnSuccess:(void (^ _Nonnull)(NSString * _Nonnull))onSuccess onError:(void (^ _Nonnull)(NSError * _Nullable))onError;
        [Export("collectOnSuccess:onError:")]
        void CollectOnSuccess(Action<NSString> onSuccess, Action<NSError> onError);

        // -(void)addPersonWithPerson:(PersonModel * _Nonnull)person completion:(void (^ _Nonnull)(NSError * _Nullable))completion;
        [Export("addPersonWithPerson:completion:")]
        void AddPersonWithPerson(PersonModel person, Action<NSError> completion);

        // -(void)application:(UIApplication * _Nonnull)application performFetchWithCompletionHandler:(void (^ _Nonnull)(UIBackgroundFetchResult))completionHandler __attribute__((availability(ios, introduced=11.0, deprecated=13.0)));
        [Introduced(PlatformName.iOS, 11, 0, message: "Use handleAllowMeBG(task: BGProcessingTask) method.")]
        [Deprecated(PlatformName.iOS, 13, 0, message: "Use handleAllowMeBG(task: BGProcessingTask) method.")]
        [Export("application:performFetchWithCompletionHandler:")]
        void Application(UIApplication application, Action<UIBackgroundFetchResult> completionHandler);

        // -(BOOL)startBiometryWithViewController:(UIViewController * _Nonnull)viewController delegate:(id<AllowMeBiometryDelegate> _Nonnull)delegate config:(AllowMeBiometryConfig * _Nonnull)config error:(NSError * _Nullable * _Nullable)error;
        [Export("startBiometryWithViewController:delegate:config:error:")]
        bool StartBiometryWithViewController(UIViewController viewController, AllowMeBiometryDelegate @delegate, AllowMeBiometryConfig config, [NullAllowed] out NSError error);
    }
}

Após colar o código acima no seu arquivo, certifique-se de que o Build Action dele (botão direito no arquivo → Build Action) esteja marcado como ObjcBindingApiDefinition.

📘

Fique atento nas mudanças!

A interface pública do AllowMeSDK pode mudar dependendo da versão usada 😔 Caso encontre algum erro nesse sentido, utilize o Sharpie (opens new window)para ajudar a iniciar a criação desse arquivo mesmo que seja necessário fazer pequenos ajustes depois. Você precisará usar como referência os headers e a bridge Swift que são encontrados dentro do XCFramework baixado anteriormente.

2.5. Definindo NoBindingEmbedding

❗️

Alerta de Bug no Xamarin (Bugamarin? Xamarug?)

É possível que você tenha problemas ao criar a sua biblioteca de associação usando XCFramework e Visual Studio (nós tivemos!). Para mais detalhes, observe a resposta para uma issue no Github do Xamarin (opens new window). A seção atual resolve esse problema para o MacOS!

Para o binding seja criado corretamente usando um XCFramework, feche o projeto no Visual Studio e abra o arquivo .csproj em um editor de texto para ter acesso ao xml que define esse csproj. Dentro da tag Project já definida, adicione o seguinte código:

<PropertyGroup>
    <NoBindingEmbedding>true</NoBindingEmbedding>
</PropertyGroup>

2.6 Compilando biblioteca de associação

Faça o build do projeto e acesse o arquivo .dll gerado dentro da pasta bin do projeto da biblioteca de associação.

2.7. Adicionando o dll ao seu app

Com o arquivo dll em mãos, abra o projeto do seu app no Visual Studio, vá até o módulo NOME-PROJETO.iOS, clique com o botão direito na pasta References → Add Project Reference. Procure a o arquivo dll nas suas pastas e adicione-o. Ele será definido como um .NET Assembly.

2.8. Inicializando o AllowMeSDK

Essa seção se refere à implementação em Xamarin do que é mostrado com mais detalhes em Primeiros Passos - Inicializar o SDK da documentação nativa.No seu módulo NOME-PROJETO.iOS, crie uma classe para executar o AllowMe e adicione um método como o mostrado abaixo para passar a sua API key e chamar o método setup.

using AllowMeWrapper;
{...}

namespace AllowMeNative.iOS
{
    public class AllowMeNative
    {
        AllowMe allowMe;

        public void apiKey(string value, Action<string> completion)
        {
            allowMe = AllowMe.GetInstanceWithApiKey(value, out NSError error);
            if(error != null)
            {
                completion(error.LocalizedDescription.ToString());
            } else
            {
                allowMe.SetupWithCompletion((setupError) =>
                {
                    if(setupError != null)
                    {
                        completion(setupError.LocalizedDescription.ToString());
                    } else
                    {
                        completion("Setup success");
                    }
                });
            }
        }
    }
}

2.8. Inicializando o AllowMeSDK

Essa seção se refere à implementação em Xamarin do que é mostrado com mais detalhes em Primeiros Passos - Inicializar o SDK da documentação nativa.No seu módulo NOME-PROJETO.iOS, crie uma classe para executar o AllowMe e adicione um método como o mostrado abaixo para passar a sua API key e chamar o método setup.

using AllowMeWrapper;
{...}

namespace AllowMeNative.iOS
{
    public class AllowMeNative
    {
        AllowMe allowMe;

        public void apiKey(string value, Action<string> completion)
        {
            allowMe = AllowMe.GetInstanceWithApiKey(value, out NSError error);
            if(error != null)
            {
                completion(error.LocalizedDescription.ToString());
            } else
            {
                allowMe.SetupWithCompletion((setupError) =>
                {
                    if(setupError != null)
                    {
                        completion(setupError.LocalizedDescription.ToString());
                    } else
                    {
                        completion("Setup success");
                    }
                });
            }
        }
    }
}

2.9 . Implementando o Device Intelligence (Contextual)

Essa seção se refere à implementação em Xamarin do que é mostrado com mais detalhes em Device Intelligence (Contextual) da documentação nativa.Para implementar o método collect, adicione à classe criada anteriormente uma chamada para o método do AllowMeSDK.

using AllowMeWrapper;
{...}

namespace AllowMeNative.iOS
{
    public class AllowMeNative
    {
        {...}

        public void Collect(Action<string, string> completion)
        {
            allowMe.CollectOnSuccess((result) =>
            {
                completion(result.ToString(), null);
            }, (collectError) =>
            {
                completion(null, collectError.LocalizedDescription.ToString());
            });
        }
    }
}

❗️

Obtendo "Error trying to collect" no simulador iOS

Durante nossos testes observamos que o SDK estava retornando consistentemente o erro "Error trying to collect" ao rodar num simulador iOS. Uma investigação mais profunda revelou que o problema acontecia na tentativa de acessar o keychain dentro do SDK e não achar o Entitlements.plist no projeto, o que acarretava um erro de permissão.

Nós observamos que o arquivo iOS.csproj não continha a referência ao Entitlements.plist dentro dos builds para simulador. Para resolver o erro, foi necessário adicionar a linha "<CodesignEntitlements>Entitlements.plist</CodesignEntitlements "dentro da tag PropertyGroup do simulador.

É possível que seja necessário adicionar essa linha em outros grupos de compilação também, então fique atento!

2.10. Implementando a Biometria Facial

Essa seção se refere à implementação em Xamarin do que é mostrado com mais detalhes em Biometria Facial da documentação nativa.Para implementar a biometria facial no Xamarin, observe a chamada da função abaixo:

using AllowMeWrapper;
{...}

namespace AllowMeNative.iOS
{
    public class AllowMeNative : AllowMeBiometryDelegate
    {
        Action<string> biometricsAction = null;

        {...}

        public void StartBiometrics(Action<string> completion)
        {
            biometricsAction = completion;
            var topVC = UIApplication.SharedApplication.KeyWindow.RootViewController;
            while (topVC.PresentedViewController != null)
            {
                topVC = topVC.PresentedViewController;
            }
            allowMe.StartBiometryWithViewController(topVC, this, new AllowMeBiometryConfig(), out NSError error);

        }
    }
}

Passamos para a função StartBiometryWithViewController a viewController mais ao topo recuperada a partir da classe UIApplication e, como segundo parâmetro, o delegate que nesse exemplo será a classe atual (this). Por isso, precisamos também implementar o delegate AllowMeBiometryDelegate na classe AllowMeNative.

📘

Dica

Na função também passamos uma AllowMeBiometryConfig. Observe a documentação nativa da Biometria Facial para verificar todas as possibilidades de customização disponíveis com a AllowMeBiometryConfig.

O AllowMeBiometryDelegate implementa a função BiometryDidFinish:

using AllowMeWrapper;
{...}

namespace AllowMeNative.iOS
{
    public class AllowMeNative : AllowMeBiometryDelegate
    {
        Action<string> biometricsAction = null;

        {...}

        public override void BiometryDidFinish(AllowMeBiometryResult biometryObject, NSError error)
        {
            string result;
            if (error == null)
            {
                result = "Payload: " + biometryObject.Payload + " | Images " + biometryObject.Images.ToString();
            }
            else
            {
                result = "Error: " + error.LocalizedDescription.ToString();
            }
            Console.WriteLine(result);
            biometricsAction.Invoke(result);
        }
    }
}

O resultado acima é transformado em String para ser mostrado na tela a partir do código em comum Xamarin. Faça as modificações necessárias para a sua implementação!

3. Conectando ao Xamarin.Forms

Para o nosso exemplo, escolhemos fazer a comunicação entre o Xamarin.Forms e as partes nativas por meio do DependencyService (opens new window). Fique livre para fazer como desejar!

Para chamar o código nativo a partir do Xamarin.Forms criamos a interface abaixo que reúne todas as funções implementadas anteriormente no Xamarin.iOS e Xamarin.Android. Você deve modificar de acordo com os produtos contratados.

public interface IAllowMeWrapper
{
    // Geral
    void apiKey(string value, Action<string> completion);
    // Device Intelligence (Contextual)
    void Collect(Action<string, string> completion);
    // Biometria Facial
    void StartBiometrics(Action<string> completion);
}

Logo na inicialização da página inicial do aplicativo, registraremos o DependencyService e chamaremos a função para setar a API key e chamar o método setup

namespace AllowMeExample.Views
{
    public partial class AllowMePage : ContentPage
    {
        IAllowMeWrapper allowMeService;

        public AllowMePage()
        {
            {...}

            DependencyService.Register<IAllowMeWrapper>();
            allowMeService = DependencyService.Get<IAllowMeWrapper>();
            allowMeService.apiKey("your-api-key-here", (result) =>
            {
                // tratar resultado
            });
        }

        {...}
    }
}

Do mesmo modo, adicionaremos a interface criada nos módulos nativos de Android e iOS:

// No Xamarin.Android
public class AllowMeNative : Java.Lang.Object, IAllowMeWrapper, {...}

// No Xamarin.iOS
public class AllowMeNative : IAllowMeWrapper, {...}

3.1. Implementando Device Intelligence (Contextual)

A função nativa que chama o AllowMe Device Intelligence (Contextual) será setada no Xamarin.Forms conforme mostrado abaixo

namespace AllowMeExample.Views
{
    public partial class AllowMePage : ContentPage
    {
        {...}

        public void OnCollectButtonClicked (object sender, EventArgs args)
        {
            allowMeService.Collect((result, errorMsg) =>
            {
                if (errorMsg != null)
                {

                    // tratar erro
                }
                else
                {
                    // enviar coleta para backend
                }
            });
        }
    }
}

3.2. Implementando Biometria Facial

A função nativa que chama o AllowMe Biometria Facial será setada no Xamarin.Forms conforme mostrado abaixo

namespace AllowMeExample.Views
{
    public partial class AllowMePage : ContentPage
    {
        {...}


        public void OnBiometricsButtonClicked(object sender, EventArgs args)
        {
            Console.WriteLine("Call biometrics in Xamarin.Forms");

            allowMeService.StartBiometrics((result) =>
            {
                // tratar resultado
            });
        }
    }
}

Observe que a permissão da câmera é necessária para o uso da Biometria Facial tanto no Android quanto no iOS. No iOS a implementação é pedida automaticamente pelo sistema desde que sejam feitas as configurações descritas na documentação nativa. No Android, por outro lado, a permissão precisa ser pedida manualmente no código. Para isso, faremos como mostra o código abaixo

namespace AllowMeExample.Views
{
    public partial class AllowMePage : ContentPage
    {
        {...}


        public AllowMePage()
        {
            {...}

            requestPermission();
        }

        private async void requestPermission()
        {
            var status = await Permissions.RequestAsync<Permissions.Camera>();
        }
    }
}

📘

Dica

Em caso dúvidas ou para ter acesso ao código fonte desse exemplo entre em contato com [email protected].