カテゴリー別アーカイブ: Xamarin

Xamarin、ザマリン、青空、晴れた空 -Xamarinで次世代mobile版データベースRealmを使う-

 こちらのサイトのメロディーとともにお楽しみください。

 Realmは、SQLite+ORMに変わるモバイルデータベースです。Realmは、NoSQLデータベース(非リレーショナルデータベース)ですので、オブジェクトを生成して手軽に永続化できます。
 現在、RealmはAndroidとiOSに対応しているので、Xamarinで使ってみます。Build Insiderに掲載されているXamarinでSQLiteを使うコンパクトなサンプルと同様なアプリをXamarin.Forms Portableで作成してみます。
 まず、Visual StudioでXamarin.Forms Portableなプロジェクトを作成します。
useRealm1
作成したら、NuGetパッケージマネージャで、realmを検索してインストールします。
 
 最初にModelクラスを作ります。グループ名だけを記録するGroup.csです。

using Realms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UseRealmApp
{
    public class Group : RealmObject
    {
        [ObjectId]
        public string ID { get; set; }
        public string Name { get; set; }
    }
}

 Realmにはいわゆるオートインクリメントな値を振る機能はありませんので、IDもStringにしてGUID値を記録することにします。
 次ぎにデータベースを操作するMemoDatabaseクラスを作成します。将来的にGroupクラスだけでなく、メモを記録するクラスなどを追加していくのでMemoDatabaseというクラス名にしました。
 GetGroups()メソッドはGroupクラスの全オブジェクトを取得します。GetGroups()メソッドの戻り値をListViewに表示します。GetGroupは指定したIDのオブジェクトを取得します。以降のメソッドについてはソースコードの下に解説を記します。

using Realms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UseRealmApp
{
    public class MemoDatabase
    {
        Realm realm;
        public MemoDatabase()
        {
            realm = Realm.GetInstance();
        }
        public IEnumerable<Group> GetGroups()
        {
            return realm.All<Group>().ToList();
        }
        public Group GetGroup(string id)
        {
            return realm.All<Group>()
                        .ToList()
                        .FirstOrDefault(x => x.ID == id);
        }
        public string SaveGroup(Group group)
        {
            var newGroup = realm.All<Group>()
                             .ToList()
                             .FirstOrDefault(x => x.ID == group.ID);

            using (var trans = realm.BeginWrite())
            {
                if (newGroup == null)
                {
                    newGroup = realm.CreateObject<Group>();
                    newGroup.ID = Guid.NewGuid().ToString();
                }

                newGroup.Name = group.Name;
 
                trans.Commit();
            }

            return newGroup?.ID;    // ? Nullable型
        }
        public string DeleteGroup(string id)
        {
            var item = realm.All<Group>()
                             .ToList()
                             .FirstOrDefault(x => x.ID == id);

            if (item == null)
                return null;

            using (var trans = realm.BeginWrite())
            {
                realm.Remove(item);
                trans.Commit();
            }

            return id;
        }
    }
}

 SaveGroupメソッドはオブジェクトを保存します。usingでトランザクション処理を囲みます。DeleteGroupメソッドは指定したIDのオブジェクトを削除します。

 app.csでページを表示します。

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

namespace UseRealmApp
{
    public class App : Application
    {
        public App()
        {
            MainPage = new MyPage();
        }
    }
    class MyPage : ContentPage
    {

        readonly MemoDatabase _db = new MemoDatabase();

        public MyPage()
        {

            var listView = new ListView
            {
                ItemsSource = _db.GetGroups(), 
                ItemTemplate = new DataTemplate(typeof(TextCell)) 
            };
            listView.ItemTemplate.SetBinding(TextCell.TextProperty, "Name");
            listView.ItemTapped += async (s, a) => {
                var group = (Group)a.Item;
                if (await DisplayAlert("削除してよろしいですか", group.Name, "OK", "キャンセル"))
                {
                    _db.DeleteGroup(group.ID); // groupの削除
                    listView.ItemsSource = _db.GetGroups(); // リストの更新
                }
            };
            var entry = new Entry
            { 
                HorizontalOptions = LayoutOptions.FillAndExpand
            };
            var buttonAdd = new Button
            {
                WidthRequest = 60,
                Text = "追加"
            };
            buttonAdd.Clicked += (s, a) => { 
                if (!String.IsNullOrEmpty(entry.Text))
                { // Entryに文字列が入力されている場合に保存
                    var group = new Group { Name = entry.Text };
                    _db.SaveGroup(group);
                    listView.ItemsSource = _db.GetGroups(); //リストの更新
                    entry.Text = "";
                }
            };

            Content = new StackLayout
            { 
                Padding = new Thickness(0, Device.OnPlatform(20, 0, 0), 0, 0),
                Children = {
                    new StackLayout {
                        Padding = 5,
                        Orientation = StackOrientation.Horizontal,
                        Children = {entry, buttonAdd} 
                    },
                    listView 
                }
            };
        }
    }
}

 ListViewにはグループ名を一覧表示し、タップしたら削除確認のダイアログを表示します。
useRealm2
 Entryにグループ名を入力して追加ボタンをタップしたら、データベースに記録してListViewを更新します。
useRealm3
 XamarinでもRealmを簡単に使うことができました。

Xamarin、ザマリン、青空、晴れた空 -住所から緯度・経度を取得する-

 こちらのサイトのメロディーとともにお楽しみください。
正式なタイトルはVisual StudioのXamarinでAndroidアプリを作る(#6) Geocorderを使うです。
6回目となる今回は、Geocorderを使って住所から緯度・経度を取得してみます。今回もdeveloper.xamarin.comのレシピ、ほとんどそのままですが、日本の住所の与え方や若干補足説明があった方が良いかと思い書きます。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Button
        android:id="@+id/geocodeButton"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/geocodeButtonText" />
    <TextView
        android:id="@+id/addressText"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
</LinearLayout>

画面には、緯度・経度引きを起動するボタンと取得した情報を表示するテキストビューを配置します。

次にMainActivityのコードです。

using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using System.Threading;
using Android.Locations;
using System.Linq;

namespace Geocorder
{
    [Activity(Label = "Geocorder", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            // Get our button from the layout resource,
            // and attach an event to it
            var button = FindViewById<Button>(Resource.Id.geocodeButton);
            button.Click += (sender, e) => {
                new Thread(new ThreadStart(() => {
                    var geo = new Geocoder(this);

                    //var addresses = geo.GetFromLocationName("50 Church St, Cambridge, MA", 1);
                    var addresses = geo.GetFromLocationName("東京都港区六本木5-11-16", 1);

                    RunOnUiThread(() => {
                        var addressText = FindViewById<TextView>
                               (Resource.Id.addressText);

                        addresses.ToList().ForEach((addr) => {
                            addressText.Append(addr.ToString() +
                                   "\r\n\r\n");
                        });
                    });
                })).Start();

            };
        }
    }
}

Geocoder#GetFromLocationNameには、日本語の住所をそのまんま与えます。
RunOnUiThreadは内部で、Handler#postを呼んでいます。
geocoder実行結果
緯度・経度のほかに郵便番号や詳細な住所も取得していますね。

Xamarin、ザマリン、青空、晴れた空 -加速度センサーを使う-

 こちらのサイトのメロディーとともにお楽しみください。
正式なタイトルはVisual StudioのXamarinでAndroidアプリを作る(#5) 加速度センサーを使うです。
 5回目となる今回は、加速度センサーを使ってみます。developer.xamarin.comのレシピを参考にします。
今回はMainActivityの全コードを掲載します。
MainActivity

using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Android.Hardware;

namespace AcceleGet
{
    [Activity(Label = "AcceleGet", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity, ISensorEventListener
    {
        static readonly object _syncLock = new object();
        SensorManager _sensorManager;
        TextView _sensorTextView;

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);
            _sensorManager = (SensorManager)GetSystemService(Context.SensorService);
            _sensorTextView = FindViewById<TextView>(Resource.Id.accelerometer_text);
        }
        public void OnAccuracyChanged(Sensor sensor, [GeneratedEnum] SensorStatus accuracy)
        {
            //throw new NotImplementedException();
        }
        public void OnSensorChanged(SensorEvent e)
        {
            lock (_syncLock)
            {
                _sensorTextView.Text = string.Format("x={0:f}, y={1:f}, z={2:f}", e.Values[0], e.Values[1], e.Values[2]);
            }
        }
        protected override void OnResume()
        {
            base.OnResume();
            _sensorManager.RegisterListener(this,
                                            _sensorManager.GetDefaultSensor(SensorType.Accelerometer),
                                            SensorDelay.Ui);
        }
        protected override void OnPause()
        {
            base.OnPause();
            _sensorManager.UnregisterListener(this);
        }
    }
}

 センサーを使うにはISensorEventListenerインターフェースをインプリメントします。
ISensorEventListenerインターフェースをインプリメントする場合は、OnAccuracyChangedメソッドとOnSensorChangedを実装する必要があります。
 加速度センサーを使うには、OnResumeメソッドで、SensorType.Accelerometerを指定して、SensorManagerのRegisterListenerでリスナーを登録します。SensorDelay.Uiはセンサーの値を取得する間隔の指定です。
 OnPauseメソッドではUnregisterListenerでリスナーの登録を解除します。
 OnSensorChangedメソッドで加速度センサーの値を取得して画面に表示します。lockで他のスレッドから更新されないように排他制御しています。

実行画面

実行画面

Xamarin、ザマリン、青空、晴れた空

 今回からタイトルを変えました。こちらのサイトのメロディーとともにお楽しみください。
正式なタイトルはVisual StudioのXamarinでAndroidアプリを作る(#4) TwoTouchMailを作るです。
 4回目となる今回は、「作ればわかる!Androidプログラミング」の第二章のサンプルアプリTwoTouchMailをXamarinで作ってみます。とは言っても、インテントでデータを渡す方法やアクティビティを指定しない暗黙的なインテントについてはこれまでの3回ですでに説明しました。
 今回説明するのは、dimens.xml、RadioGroupの使い方、そしてStrings.xmlに登録した文字列をC#のコードで取得する方法です。
dimens.xmlにはpaddingで使うマージンのサイズを登録します。dimens.xmlは新規作成するXMLファイルです。
dimen.xml

<?xml version="1.0" encoding="utf-8" ?>
<resources>
  <!-- Default screen margins, per the Android Design guidelines. -->
  <dimen name="activity_horizontal_margin">16dp</dimen>
  <dimen name="activity_vertical_margin">16dp</dimen>
</resources>

 そして、レイアウトファイルにマージンを指定します。下記のサンプルはお迎えを頼むPickUp.axmlです。
RelativeLayoutの内側に余白を作るためにdimens.xmlの縦横marginをpaddingXXXXXXに指定しています。
PickUp.axml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">
    <TextView
        android:text="@string/subject"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:id="@+id/textView" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/editText"
        android:layout_below="@+id/textView"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:text="@string/button_pickup" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="@string/place"
        android:id="@+id/textView3"
        android:layout_below="@+id/editText"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginTop="16dp" />
    <RadioGroup
        android:id="@+id/rg_place"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView3"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true">
        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/station"
            android:checked="true"
            android:id="@+id/radioButton1" />
        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/school"
            android:id="@+id/radioButton2" />
        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/always"
            android:id="@+id/radioButton3" />
    </RadioGroup>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/button_send"
        android:id="@+id/button"
        android:layout_below="@+id/rg_place"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

PickUp2
これで、TextViewやRadioGroupの周囲に空白ができます。

 次にRadioGroupの使い方に進みます。PickUp.axmlにあるようにRadioGroupは複数のRadioButtonをグループ化して、択一にするために使います。この例の場合、一番上のラジオボタンのandroid:checkedが”true”になっています。
 RadioGroupを使うPickUpActivityクラスのコードをみていきましょう。
PickUpActivityクラス

   public class PickUpActivity : Activity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Create your application here
            SetContentView(Resource.Layout.PickUp);

            var btnSend = FindViewById<Button>(Resource.Id.button);

            btnSend.Click += delegate {
                RadioGroup rgPlace = (RadioGroup)FindViewById(Resource.Id.rg_place);
                int checkedId = rgPlace.CheckedRadioButtonId;
                String strPlace = ((RadioButton)FindViewById(checkedId)).Text;
                EditText edit01 = (EditText)FindViewById(Resource.Id.editText);
                String title = edit01.Text;

                Context context = this;
                Android.Content.Res.Resources res = context.Resources;

                var uri = Android.Net.Uri.Parse("mailto:" + res.GetString(Resource.String.mail_to));

                Intent intent = new Intent(Intent.ActionSendto, uri);
                intent.PutExtra(Intent.ExtraSubject, title);
                intent.PutExtra(Intent.ExtraText, strPlace + "に迎えにきて");
                StartActivity(intent);

            };

        }
    }

 
 btnSendがクリックされたら、選択されているRadioButtonのTextをメール本文に含めるようにしたいのですが、まず選択されているラジオボタンのIDを次のコードで取得します。
int checkedId = rgPlace.CheckedRadioButtonId;
Android SDKをJavaで使っている場合、選択されているボタンのIDはメソッドで取得します。Xamarinの場合はプロパティとして取得できますので、若干お得感があります。
 次にStrings.xmlに登録した文字列を取得する方法ですが、Android.Content.ResのResourcesクラスのGetStringメソッドで取得できます。Resource.String.mail_toはStrings.xmlに登録した送信先のメールアドレスです。
 そして、ActionSendtoを指定してIntentを生成し、PutExtraメソッドでタイトルや本文を付与し、StartActivityメソッドで暗黙的インテント(明示的に遷移先を指定しないインテント)を発行します。
 すると、メールアプリが起動します。
mailapp2
 これで簡単にメールを送るアプリができました。

Visual StudioのXamarinでAndroidアプリを作る(#3) 暗黙的なインテント

暗黙的なインテントとは遷移先のアクティビティを指定しないインテントのこと
以下の例はIntentにurlとそれを見たいのだということを引数で指定して、
StartActivityメソッドを実行する。
button.Click += delegate {
  var uri = Android.Net.Uri.Parse(“http://www.bing.co.jp”);
  Intent intent = new Intent(Intent.ActionView, uri);
  StartActivity(intent);
};
implicit2
実行すると、ブラウザが起動してwww.bing.co.jpが表示される。

Visual StudioのXamarinでAndroidアプリを作る(#2) Activity間のデータ渡し

Activity間でデータを渡す
遷移元ActivityではIntentを生成し、PutExtraメソッドで渡したいデータを積む。
button.Click += delegate {
  var secondActivity = new Intent(this, typeof(SecondActivity));
  secondActivity.PutExtra(“MyData”, “FirstActivityの方から来ました”);
  StartActivity(secondActivity);
};
遷移先Activityではデータ型に応じたGetXXXXXExtraメソッドでデータを取得する。
var txtView1 = FindViewById(Resource.Id.textView1);
txtView1.Text = Intent.GetStringExtra(“MyData”) ?? “Data not available”;

first
遷移元
second
遷移先

Visual StudioのXamarinでAndroidアプリを作る(#1)

Activityの遷移をするコードは以下のようになる。Intentはどこにも出てこない。
button.Click += delegate {
  StartActivity(typeof(SecondActivity));
};
Activityとそのレイアウトファイルはそれぞれ作成する必要がある。
Android Studioのようにレイアウトファイルを自動作成してくれない。
追加したActivityをAndroidManifest.xmlに手動で追加する必要はない。
Monitor_err
現在の問題点はAndroid Device Monitorが起動しないこと。