반응형

2018년 첫 회사를 퇴사하고 잠시 앱 개발에 관심을 가졌기에 이미 자주 사용한 C#을 활용할 겸 자마린을 공부했었다.

당시에도 이미 국내 자마린 카페가 서서히 죽어가고 있었는데, 그래도 온라인 강의도 있기는 있었으니 사용자는 있었다.

하지만 Fluter 의 등장으로 많은 자마린 개발자들이 옮기는 흔적들이 곳곳에 있었는데, 2024년에는 개발자들 사이에서는 사실상 죽은 프레임 워크 취급을 하는 것 같다.

Xamarin.Form 이 죽을 수 밖에 없는 이유에 대해서 생각을 해보면 그럴만한 것이 Xamarin.Form 이 동작하는 구조는 각 안드로이드, iOS, (그리고 Windows phone)를 감싼형태의 프레임워크였고, 하나의 소스코드를 사용한다고 하지만, 이것이 완전하지 않았다. 그리고 C#의 언어적으로 막는 이중 참조 구조가 필수적으로 필요한 구조가 되었기 때문에 2번씩 빌드해야 적용되는 경우도 발생했다.

따라서 신경써야 할 것이 많은 프레임 워크였고, 다른 경쟁 프레임 워크에 비해서 경쟁력이 떨어졌다(특히 Fluter). MS 에서 C# 언어 자체를 발전 시킨것처럼 지속적으로 발전을 시켰으면 다행이었겠지만, 그러지 않았다. 아마 구글링을 해도 2018년 기점으로 새롭게 올라운 글이 많이 않을 것이다.

아 물론 MS사 웹사이트를 빼고

반응형
반응형

Xamarin.Form : 안드로이드 에뮬레이터 HAX Module 에러

접하게 된 배경

무슨 잘 못을 했는지 모르겠지만, Xamarin.Form을 공부하려고 할 때 마다 안드로이드 에뮬레이터 문제가 항상 있었왔다.

 VS 2017과는 독립적으로 안드로이드 에뮬레이터(AVD Manager)는 독립적인 툴이다. 때문에 안드로이드 스튜디오를 설치를 해도 설치가 된다. 가상 기기를 구성한 것을 제대로 기동하기 위해서는 Android SDK 에서 적합한 버전을 준비해야 한다.

 여기까지 무사히 마치고 테스트에 사용할 가상 기기를 생성한뒤에 실행해서 잘되면 다행이지만, 실행이 안되는 경우가 있다.


 필자의 경우 다음과 같은 에러 메시지를 보여준 다음에 실행이 안되었다.





해결 방법

 항상 에러 메시지를 자세히 읽어 보면 답이 있다. 필자의 경우 Device Manager에서 HAX Module version 이 낮다고 하는데, 이게 뭔지 몰라서 다시 설치해보고 업데이트를 시작해 보았지만, 해결이 안되었었다. 알고보니 HAXM 이라는 인텔에서 제공하는 패치를 직접 해야한다.


필자는 haxm-windows_v7_2_0 버전을 다운로드 받았다.


인텔 HAXM 패치 페이지 링크


 구글에서 HAX module version 을 키워드로 친다음에 검색된 인텔 웹 페이지에 접속해서 다운로드 받아서 설치해도 된다. 설치방법은 그냥 다운로드 받은 압축파일의 압축을 해제하고 안에 있는 배치파일을 실행하거나 intelhaxm-android.exe를 실행하면 된다.


 개인적인 생각에는 MS에서 자동으로 업데이트를 제공해 주었으면 좋았을 텐데 아쉬운 점이 있다.


참고자료

이 양반의 블로그로 해당 모듈의 정체를 알게 되었다.



반응형
반응형

Xamarin.Form : 빠른 시작(첫 앱 만들기)

작성 배경

 Xamarin.Form가 나온지 시간이 괘 지났음에도 한글문서가 많지 않은 것같다. 사실 튜토리얼에 있는 영문문서가 읽기에 어려운 편은 아니다. 그러나 단의 영어로 되어 있다는 점때문에 진입장벽이 생기는 것 같아서 틈틈히 정리를 한다. 물론 정리하다가 중단하게 될 수 도 있으리라...


 본문의 내용은 Xamarin.form 공식 웹페이지의 Getting Started -> Hello, Xamarin.Form -> part 1: Quickstart를 약간의 번역과 의역을 첨가하여 작성한다.



Xamain.Forms 빠른 시작


프로젝트 만들기

새프로젝트 항목 중 Visual C# 하위 목록중에서 Cross-platform를 선택을 하면 우측 괄호에 Xamarin.Form이 보이는 항목을 클릭한다.


프로젝트 이름은 "Phoneword" 로 한다.


실제 여러개의 프로젝트가 하나의 앱을 만듣는 것이기 때문에 가급적 솔루션용 폴더를 체크하도록 한다.


확인 버튼을 클릭을 하면, 크로스플래폼 앱에 대해서 세부 설정을 할 수 있다. "Blank App"을 선택하고 .NET Standard를 선택을 하고 프로젝트를 생성한다.


(먼저 Androd 프로젝트의 MainActivity.cs 파일을 열어보고 에러 메시지가 뜨는지 확인을 해야한다. Xamarin.Form이 메이저 버전이 업데이트 되면서 일부 호환이 되지 않아서 에러가 나는 경우가 있는데, 이때는 미련없이 재설치를 하자.)


여러개의 프로젝트중에서 Phoneword 프로젝트내의 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="Phoneword.MainPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS" Value="20, 40, 20, 20" />
            <On Platform="Android, WinPhone, Windows" Value="20" />
        </OnPlatform>
    </ContentPage.Padding>
    <StackLayout>
      <Label Text="Enter a Phoneword:" />
      <Entry x:Name="phoneNumberText" Text="1-855-XAMARIN" />
      <Button x:Name="translateButon" Text="Translate" Clicked="OnTranslate" />
      <Button x:Name="callButton" Text="Call" IsEnabled="false" Clicked="OnCall" />
    </StackLayout>
</ContentPage>


변경이 끝나면, 저장(Ctrl + s)을 한다.


이제 솔루션 탐색기(Solution Explorer)의 (+)버튼을 누르면 같은 이름의 MainPage.xaml.cs 파일이 보인다(혹은 소스코드 열기로 확인할 수 있음). 해당 파일을 수정한다.


 단, 주의 해야 할점이 에러가 발생하는 코드가 있는데, 이는 진행하면서 해결하니 조급해하지 말자.


MainPage.xaml.cs 입력받은 숫자문자를 숫자로 변환을 해주는 기능이다.


using System;
using Xamarin.Forms;

namespace Phoneword
{
    public partial class MainPage : ContentPage
    {
        string translatedNumber;

        public MainPage ()
        {
            InitializeComponent ();
        }

        void OnTranslate (object sender, EventArgs e)
        {
            translatedNumber = Core.PhonewordTranslator.ToNumber (phoneNumberText.Text);
            if (!string.IsNullOrWhiteSpace (translatedNumber)) {
                callButton.IsEnabled = true;
                callButton.Text = "Call " + translatedNumber;
            } else {
                callButton.IsEnabled = false;
                callButton.Text = "Call";
            }
        }

        async void OnCall (object sender, EventArgs e)
        {
            if (await this.DisplayAlert (
                    "Dial a Number",
                    "Would you like to call " + translatedNumber + "?",
                    "Yes",
                    "No")) {
                var dialer = DependencyService.Get<IDialer> ();
                if (dialer != null)
                    dialer.Dial (translatedNumber);
            }
        }
    }
}


에러 메시지에 겁먹지 말고 Ctrl + s 를 클릭하여 변경사항을 저장한다.


 같은 솔루션 탐색기에서 같은 프로젝트의 App.xaml 하위에 있는 App.xaml.cs를 열고 아래와 같이 수정을 한다.


using Xamarin.Forms;
using Xamarin.Forms.Xaml;

[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Phoneword
{
    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();
            MainPage = new MainPage();
        }

        protected override void OnStart()
        {
            // Handle when your app starts
        }

        protected override void OnSleep()
        {
            // Handle when your app sleeps
        }

        protected override void OnResume()
        {
            // Handle when your app resumes
        }
    }
}


역시 변경사항을 저장한다.


솔루션 탐색기에서 Phoneword 프로젝트에서 마우스 우클릭을 하여 추가(Add)에서 새항목(New Item)을 선택한다.


 Visual C#의 code 항목에서 클래스(Class)를 선택하고 이름을 "PhoneTranslator"로 작성하고 추가(Add)한다.


생성된 PhoneTranslator.cs를 다음과 같이 수정한다.

(이때 주의 할 점이 namespace 이름을 바꾼다는 것을 간과 하지 말자)


using System.Text;

namespace Core
{
    public static class PhonewordTranslator
    {
        public static string ToNumber(string raw)
        {
            if (string.IsNullOrWhiteSpace(raw))
                return null;

            raw = raw.ToUpperInvariant();

            var newNumber = new StringBuilder();
            foreach (var c in raw)
            {
                if (" -0123456789".Contains(c))
                    newNumber.Append(c);
                else
                {
                    var result = TranslateToNumber(c);
                    if (result != null)
                        newNumber.Append(result);
                    // Bad character?
                    else
                        return null;
                }
            }
            return newNumber.ToString();
        }

        static bool Contains(this string keyString, char c)
        {
            return keyString.IndexOf(c) >= 0;
        }

        static readonly string[] digits = {
            "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ"
        };

        static int? TranslateToNumber(char c)
        {
            for (int i = 0; i < digits.Length; i++)
            {
                if (digits[i].Contains(c))
                    return 2 + i;
            }
            return null;
        }
    }
}


작성이 완료되었으면, 역시 저장한다.


Phoneword 프로젝트에 이번에는 Interface를 추가한다. 방법은 방금전과 마찬가지로 솔루션 탐색기에서 Phoneword 프로젝트에서 "우클릭 >> 추가 >> 새 항목" Visual C#의 code 인터페이스(interface)를 선택한뒤 IDialer로 이름을 적고 생성한다.


코드는 다음과 같이 수정한다.


namespace Phoneword
{
    public interface IDialer
    {
        bool Dial(string number);
    }
}


이 코드는 나중에 다른 프로젝트들에서 상속한다. 역시 저장을 한다.



이제, Dialer를 상속하자. 솔루션 탐색기에서 Phoneword.iOS 프로젝트에 우클릭을 한뒤에 "추가"에서 "새 항목"을 선택한다.


 새 항목 화면에서 Apple의 하위의 code를 선택하고, 클래스(Class)를 선택한다. 이때 클래스가 여러개 보이는데, 처음 보이는 것을 선택하면 된다.


 클래스 명은 "PhoneDialer"로 하여 생성한다.


코드는 다음과 같이 수정한다.


using Foundation;
using Phoneword.iOS;
using UIKit;
using Xamarin.Forms;

[assembly: Dependency(typeof(PhoneDialer))]
namespace Phoneword.iOS
{
    public class PhoneDialer : IDialer
    {
        public bool Dial(string number)
        {
            return UIApplication.SharedApplication.OpenUrl (
                new NSUrl ("tel:" + number));
        }
    }
}


변경이 완료되었으면, 저장을 한다.


이번에는 안드로이드 쪽이다. Phoneword.Android 프로젝트에 우클릭 하고 추가의 새 항목을 선택한다.


추가 창에서 visual C#하위의 Android를 선택한뒤 클래스(Class)를 선택하여 이름을 아까와 동일한 PhoneDialer로 작성하고 생성한다.


아래와 같이 코드를 수정한다(이때 임포트 되는 모듈이 많으므로 빼먹지 않도록 주의한다).


using Android.Content;
using Android.Telephony;
using Phoneword.Droid;
using System.Linq;
using Xamarin.Forms;
using Uri = Android.Net.Uri;

[assembly: Dependency(typeof(PhoneDialer))]
namespace Phoneword.Droid
{
    public class PhoneDialer : IDialer
    {
        public bool Dial(string number)
        {
            var context = MainActivity.Instance;
            if (context == null)
                return false;

            var intent = new Intent (Intent.ActionCall);
            intent.SetData (Uri.Parse ("tel:" + number));

            if (IsIntentAvailable (context, intent)) {
                context.StartActivity (intent);
                return true;
            }

            return false;
        }

        public static bool IsIntentAvailable(Context context, Intent intent)
        {
            var packageManager = context.PackageManager;

            var list = packageManager.QueryIntentServices (intent, 0)
                .Union (packageManager.QueryIntentActivities (intent, 0));

            if (list.Any ())
                return true;

            var manager = TelephonyManager.FromContext (context);
            return manager.PhoneType != PhoneType.None;
        }
    }
}


변경이 완료되었으면 저장을 한다.



솔루션 탐색기에서 Phoneword.Android 프로젝트를 더블 클릭하여 MainActivity.cs 파일을 열어서 다음과 같이 수정한다(원문은 더블클릭하라고 했지만, 해당되는 이름의 파일을 클릭해도 된다).


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

namespace Phoneword.Droid
{
    [Activity(Label = "Phoneword", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        internal static MainActivity Instance { get; private set; }

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

            base.OnCreate(bundle);

            Instance = this;
            global::Xamarin.Forms.Forms.Init(this, bundle);
            LoadApplication(new App());
        }
    }
}


작성이 완료되면 저장을 한다.


Phoneword.Android 프로젝트를 우클릭하여 속성을 선택한다. 속성에서 "Android 매니페스트"(Android Manifest)항목을 선택하고 하단의 Required permissions: 항목중 "CALL_PHONE"항목을 체크한다.


역시 변경되었으면 저장한다.



이제 UWP를 수정하자.

솔루션 탐색기에서 Phoneword.UWP 프로젝트에 우클릭을 한뒤 추가(Add)에 새 항목(New Item)을 선택한다. 선택화면에서는 Visual C# 에서 Code를 선택하여 클래스(Class)를 선택한다. 이름은 PhoneDialer로 작성하여 생성한다.


생성된 클래스는 다음과 같이 작성한다(아직 어셈블리를 추가하지 않았기 때문에 에러가 보이는 경우도 있으나 조급할 필요가 없다).


using Phoneword.UWP;
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Calls;
using Windows.UI.Popups;
using Xamarin.Forms;

[assembly: Dependency(typeof(PhoneDialer))]
namespace Phoneword.UWP
{
    public class PhoneDialer : IDialer
    {
        bool dialled = false;

        public bool Dial(string number)
        {
            DialNumber(number);
            return dialled;
        }

        async Task DialNumber(string number)
        {
            var phoneLine = await GetDefaultPhoneLineAsync();
            if (phoneLine != null)
            {
                phoneLine.Dial(number, number);
                dialled = true;
            }
            else
            {
                var dialog = new MessageDialog("No line found to place the call");
                await dialog.ShowAsync();
                dialled = false;
            }
        }

        async Task<PhoneLine> GetDefaultPhoneLineAsync()
        {
            var phoneCallStore = await PhoneCallManager.RequestStoreAsync();
            var lineId = await phoneCallStore.GetDefaultLineAsync();
            return await PhoneLine.FromIdAsync(lineId);
        }
    }
}


변경이 완료되었으면 저장한다.


 프로젝트 탐색기에서 참조(Reference)를 우클릭한뒤 "참조 추가"(Add Reference)를 클릭한다. 

좌특의 Universal Windows의 하위 확장(Extensions)을 클릭한다.


 항목들중 Windows Mobile Extensions for the UWP 모듈 최신(작성시점 10.0.162..)을 체크하고, 확인 버튼을 클릭한다.

(종종 체크가 이미 되어 있는 경우가 있는데 이 때는 변경할 것이 없다.)



Phoneword.UWP 프로젝트 하위의 "Package.appxmanifest" 항목을 연다.

상위 탭버튼 중에 "기능"(Capabilities)를 선택한뒤에 좌측 기능중 "전화 통화"(Phone Call)를 체크 하고 저장한다.



이제 빌드할 시간이다. 먼저 지금까지 작성한 코드중에 에러가 있는지 확인을 해본다. 종종 IDailer를 찾지 못한다는 에러가 보일 수 있는데 이 경우 Phoneword 프로젝트를 빌드를 해주면 된다. Phoneword 프로젝트에 우클릭하여 빌드를 클릭하여 빌드하자.


Phoneword 빌드가 끝나면, 솔루션 탐색기에서 Phoneword.UWP 프로젝트를 우클릭을 한뒤 "시작프로젝트로 설정"(Set as StartUp Project)를 클릭한다. 이제 상단의 녹색화살표가 있는 "로컬 컴퓨터"(Local Machine)를 클릭한다(혹은 단축키 F5).


기다리면, UWP로 빌드된 프로그램이 실행된것을 확인할 수 있다.



참조자료

자바린 공식 페이지(영문)



반응형

+ Recent posts