20200116のAndroidに関する記事は6件です。

いまさらFragmentを試してみる

はじめに

Androidのプログラムを初めて何年も経つけどFragmentを触ったことがほとんどないというちょっと恥ずかしいかもしれない告白w
まぁ、SurfaceViewに描画してゲーム作る程度なのでFragment無くてもよかったというのもあるけど。
最近、お仕事でもAndroidのコードに触れる機会もあり、そろそろちゃんと分かっておかないと、やばい!
というわけで、ネットで何かないかな?と探してたらあったので試してみる。

参考にしたのはこちら
Android はじめてのFragment

いや、ほんと初めてみたいなものなのでw
上記のページを参考にしたところ、LegacyになってるRelativeLayoutを使てたり、知識の足りないせいで思うようにいかないところがあったりと、久々にAndroidのコード書いてよいウォーミングアップになりました。

とりあえず、参考サイトの内容をなぞるように書くなら、参考サイトを見てもらえばいいので、個人的に調べたことを追加しつつ、適当に端折りながら書いていきます(自分が後で参考にできればいいくらいの気持ちで)

Androidのプロジェクトの作成

Empty Activityでプロジェクトを作ります。
image.png

今のトレンドならKotlinがいいと思いますが、Javaを選んでやりました。
image.png

Fragmentのレイアウトとクラスの作成

こんな感じでレイアウトを作成します
image.png

で、作ったレイアウトを開いて、TextViewとButtonを追加します。
あと、LinerLayoutのlayout_widthをmatch_parent、layout_heightをwarp_contentにします。
こんな感じになっていればOK
image.png

次にFragmentを実装したクラスを作成します。
image.png

クラスができたら、onCreateView()メソッドとonViewCreated()メソッドをオーバーライドして、中身を実装します。
参考元そのまんまですが、

MainFragment.java
public class MainFragment extends Fragment {

    private TextView mTextView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saveInstanceState) {
        super.onCreateView(inflater, container, saveInstanceState);
        return inflater.inflate(R.layout.fragment_main, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle saveInstanceState){
        super.onViewCreated(view, saveInstanceState);
        mTextView = view.findViewById(R.id.textView);
        view.findViewById(R.id.button).setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view) {
                mTextView.setText(mTextView.getText() + "!");
            }
        });
    }
}

onCreateView()メソッドは、Fragmentが最初にUIを描画するときにシステムから呼び出されるメソッドになります。
なので、ここで、FragmentにViewを設定しています。
ちなみに、FragmentクラスのコンストラクタにリソースIDを指定したり、@ContentViewアノテーションで指定できるらしいのですが、今回試したSDKのバージョン(26)ではできなかった。
どこかでこの辺は調べて明確にしておきたいところ。

onViewCreated()メソッドは、公式のフラグメントのサイトのライフサイクルの図には登場しませんが、onCreateViewメソッドの後にシステムから呼び出されるメソッドです。
初期化処理はonCreateView()ではなくonViewCreated()でやってねと公式の(Fragmentのリファレンス)[https://developer.android.com/reference/kotlin/androidx/fragment/app/Fragment.html#oncreateview]に書いてあります。
なので、onViewCreated()メソッドで初期化処理を行っています。

ActivityにFragmentを追加する

レイアウトに追加してもいいんですが、コードで追加する方法が好みなので、コードでFragmentをActivityに追加します。
Activityのコードはこんな感じ。

MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MainFragment fragment1 = new MainFragment();
        MainFragment fragment2 = new MainFragment();
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.add(R.id.container, fragment1);
        transaction.add(R.id.container, fragment2);
        transaction.commit();
    }
}

同じフラグメントを追加したかったのでこんな感じになっています。
参考元だと、

transaction.add(R.id.container, fragment1);

上記をもう一度呼び出せばいいように読めますが、上記を2回呼び出すと、落ちます。
なので、MainFragmentクラスのインスタンスはそれぞれ作る必要があります。

これで、ActivityにFragmentを追加することができました。
image.png

さてさて、今回はここまで。
もう少し、Fragmentを使うシーンを考えて実装したいところ。
何か考えよう。

参考

Android はじめてのFragment
Fragmentの初期化はonViewCreatedかonActivityCreatedで
知らずに作って大丈夫?Androidの基本的なライフサイクルイベント31選

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Mobile Blazor Bindings は「Xamarin.Forms アプリ生成器」でした

BlazorでiOS/Androidネイティブアプリケーションを開発可能にする「Mobile Blazor Bindings」、マイクロソフトが発表 - Publickey

これなんですけど。「ざまりん乙」的な声が今回もチラホラ聞かれたので「マジか!それはすごい」と思って試してみました。

サンプルアプリを動かしてみた

こちらの手順の通りで、簡単に試すことができます。1

image.png

まあ、ソリューションを見てみただけで Xamarin(Xamarin.Forms アプリ) なんですけども。

inspect してみた

続いて、Android 向けのプロジェクトをエミュレータで実行し、Android Studio 付属の Device Monitor で inspect してみました(もしかしてガワネイティブ?という可能性もあったので)。

image.png

FrameLayout や RelativeLayout などの Android ネイティブ部品が並んでいるので確かに "ネイティブ" でした。
また、PlatformRenderer とか ButtonRenderer、これらは Xamarin.Forms アプリを inspect した時と同じ感じです。

Mobile Blazor Bindings のソースコードを見てみた

ソースコードはこちらです(Open by default 素晴らしい)。

ここらへん に、それっぽい記述を見つけることができます。

using XF = Xamarin.Forms;

namespace Microsoft.MobileBlazorBindings.Elements
{
    public class Button : View
    {
        static Button()
        {
            ElementHandlerRegistry
                .RegisterElementHandler<Button>(renderer => new ButtonHandler(renderer, new XF.Button()));
        }
(以下略)

サンプルアプリに Razor テンプレート(*.razor) に記述した <Button> という要素は、上記の処理で Xamarin.Forms.Button にマップされるように見えます。

つまり

「Razor の仕組みを使って Xamarin.Forms アプリを作ることができるのが Mobile Blazor Bindings」

ということのようです。 UI用DSLから Android/iOS 向けには Xamarin.Forms が使われる点は、Uno Platform にも近いと思います。

なにがおいしいの?

Blazor は「We B Assembly + Razor」の造語だっと記憶してますが、昨日の .NET Conf Focus on Blazor のまとめを見ると、

「WebAssembly 使う使わん関係なく、とにかく Razor 技術から様々なプラットフォーム向けアプリを生成するフレームワークが Blazor」
という事になったようですね(Mobile Blazor Bindings に wasm 技術の欠片は見当たりませんでした)。

という事で、.razor テンプレートやそれが属するプロジェクトに記述された UI や C# コードは、Webアプリ/モバイルアプリ/デスクトップ向けアプリとしても再利用できますよ、という目論見のようです。

Razor は、ASP.NET を使ってWebフロントエンドを開発するための技術として普及している(と思う)ので、その流れでカバー範囲を広げたい人には刺さるでしょう(一方 XAML を主戦場とする人には前述の Uno Platform の方が刺さるでしょうか)。

「「「ネイティブAndroid のラッパーである Xamarin.Android」、をラップした Xamarin.Forms」をさらにラップした Mobile Blazor Bindings」、、、というゴテゴテ感、個人的にはキライじゃないし、それなりに基盤がしっかりしていないとさすがに崩れ落ちるので、.NET基盤 ってすごいなあ、と思います。


  1. 簡単に動かせたのは Xamarin.Forms アプリ開発環境は既に構築済みだったこともありw 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Android] ServiceとIntentServiceの違い

Serivce

Serviceについては以前触れたので、こちらの記事を参照願いたい。

・Service
https://qiita.com/QiitaD/items/5c313076b7b99823ce1a

・IntentService
https://qiita.com/QiitaD/items/96e70cd189d0c4385393

終了方法

Serviceは終了するときにStopService()かstopSelf()を明示的に書かなければ終了しない。しかし、IntentServiceの場合はそれをする必要がなく、処理が終わったら自動的に終了してくれる。

スレッド

ServiceはActivityと同じUIスレッド(メインスレッド)で動いているが、IntentServiceはメインスレッドとは別のスレッド(内部のHandlerThread)で動いている。

そのため例えば、HTTP通信をServiceに任せる時、IntentServiceは非同期処理に向いているので通信できる。しかしServiceクラスで行おうとすると「NetworkOnMainThreadException」が発生する。

またIntentServiceはUIスレッドで動いていないので、Viewを操作できない。(BroadcastReceiverやCallBackを使わなければならない。)
ネットに転がっているサンプルにはToastをプログラム内に組み込んでいるものもあったが、Toastも出なかった。(Handler使えば可能になるという記事もあった https://qiita.com/wakwak/items/5a03127aba3effa845f8)

感想

非同期処理関連はService以外にもAsyncTaskがあるのでかなり厄介に感じた。向いてないクラスを使うと、品質を落としかねないので気を付けたい。

参考URL

・Android 非同期処理についてまとめるメモ
https://qiita.com/tk_daze/items/bc83c69750e5f2e4015c

・IntentService と Serviceの違い
https://oc-technote.com/android/intentservice%e3%80%80%e3%81%a8%e3%80%80service%e3%81%ae%e9%81%95%e3%81%84/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

flutter 全面にローディングを表示させるサンプル

実現したいこと

なにか処理中で、操作を受け付けれないような状態の時に、
全画面でローディングを表示させるやりかたについて書きたいと思います。

動作の様子

ボタンを押すとローディングが表示。2秒後に自動的に閉じます。

動作コード

こちらに一式用意しています。
https://github.com/quqjp/flutter-test

コード箇所はこちら。
https://github.com/quqjp/flutter-test/tree/master/lib/sample1

構成

ローディング表示のWidget OverlayLoadingMolecules を作成。

組み込む側メインのコードにて、Stack Widget を用いて最前面に配置します。

ローディングWidget OverlayLoadingMolecules

表示の状態 visible プロパティを持ち表示を外部から制御できるようになっています。
非表示時は、空のContainer()が表示されます。
くるくるローディング表示自体は、CircularProgressIndicator Widget を用いた。

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

//
// 全面表示のローディング
//
class OverlayLoadingMolecules extends StatelessWidget {
  OverlayLoadingMolecules({@required this.visible});

  //表示状態
  final bool visible;

  @override
  Widget build(BuildContext context) {
    return visible
        ? Container(
            decoration: new BoxDecoration(
              color: Color.fromRGBO(0, 0, 0, 0.6),
            ),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                CircularProgressIndicator(
                    valueColor: new AlwaysStoppedAnimation<Color>(Colors.white))
              ],
            ),
          )
        : Container();
  }
}

組み込む側メインのコード

//dart package
import 'package:flutter/material.dart';

//third party package

//my package

//same package
import "overlay_loading_molecules.dart";

//
// 全面にローディングを表示させるサンプル
//
class Sample1Page extends StatefulWidget {
  Sample1Page({Key key, this.title}) : super(key: key);

  // タイトル
  final String title;

  @override
  _State createState() => _State();
}

class _State extends State<Sample1Page> {
  //ローディング表示の状態
  bool visibleLoading = false;

  @override
  void initState() {
    super.initState();
  }

  //
  //
  // ボタンハンドラ
  onPressMyButton() async {
    //ローディングを表示
    setState(() {
      visibleLoading = true;
    });

    //2秒待つ
    await Future.delayed(const Duration(milliseconds: 2000), () {});

    //ローディングを非表示
    setState(() {
      visibleLoading = false;
    });
  }

  ///
  ///
  ///
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      //
      body: ClipRect(
        child: Stack(
          fit: StackFit.expand,
          overflow: Overflow.clip,
          children: <Widget>[
            Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                //
                //ローディングを表示させるためのボタン
                RaisedButton(
                  onPressed: onPressMyButton,
                  child: Text('ローディングを表示', style: TextStyle(fontSize: 20)),
                ),
              ],
            ),
            //
            //ローディング
            OverlayLoadingMolecules(visible: visibleLoading)
          ],
        ),
      ),
    );
  }
}

まとめ

flutter で全面にローディングを表示させる方法をまとめました。
flutter初学者のため、アドバイスなどいただけると嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【未解決】Samsung端末だとFlutterの文字入力がバグる案件

どういうバグなんですか?

入力してない文字がガンガン入ってくる
72007589-4020a180-320f-11ea-87fc-4393229842e8.gif
ソース元

Q.一般開発者に出来る対応は? A.ほぼ無い

マジで勘弁してください(白目

バグの発生原因

Samsung端末の 予測変換 機能に問題があるとのこと。
OS周辺のブラックボックスに起因しているのがキツい

英数字限定のTextFieldにだけ使える対応

予測変換がバグの温床なんだから、予測変換使わなきゃいいじゃん?
ということで、そういう設定の仕方があるみたいです。

1.まず device_info ライブラリをインストールします
https://pub.dev/packages/device_info

2.こういう感じでサムスン端末判定を行う

bool samsungKeyboard = false;
if (Platform.isAndroid) {
  var info = await DeviceInfoPlugin().androidInfo;
  samsungKeyboard =
    (info?.manufacturer?.toLowerCase()?.contains("samsung") ?? false);
} else {
  samsungKeyboard = false;
}

3.サムスン端末判定で keyboardType の設定を変える

TextField(
  keyboardType: samsungKeyboard ?
    TextInputType.visiblePassword : 
    TextInputType.emailAddress,
  autoFocus: false,
)

この対応が根本的な改善にならない理由

  1. 全部の TextField にこれ対応するの単純にキツイし漏れやすい
  2. 予測変換の禁止強制、ユーザビリティ最悪ですよねこれ
  3. 複数行入力に対応しようとすると
TextField(
  keyboardType: TextInputType.multiline,
  maxLines: null,
)

を設定する必要がある(引用元)ので、keyboardTypeが重複して駄目

という感じで、現実的にどうにもならないです。

Flutter公式側の動向

https://github.com/flutter/flutter/issues/42273
対応自体は行ってくれているみたいですが、2020/01/16現在、
対応マイルストーンは2020年の2月および5月とそれなりに長めの設定。
微妙に遠いなぁ……(遠い目

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】[Serializable]なSoundPlayerという名前のクラスを作るとビルドは通るがAndroid実機で落ちる

はじめに

タイトルままです。
調べても対処法が出てこず、2時間ぐらい詰まったのでメモ。

環境

開発環境
Unity 2019.2.17f1
Windows 10
確認Android環境1
FireHD 10(Fire OS 5.3.7.0)
確認Android環境2
BlueStacks4(OnePlus 5)

エラー本文

logcatで怪しかったところ.txt
01-15 01:46:13.567: E/Unity(15508): The file '/data/app/com.XXX.XXX-2/base.apk/assets/bin/Data/level0' is corrupted! Remove it and launch unity again!
01-15 01:46:13.567: E/Unity(15508): [Position out of bounds!]

解決法

[System.Serializable]なSoundPlayerというクラスの名前を別のものにする(今回はSoundProviderという名前にリネームして解決)

Before

SoundPlayer.cs
namespace Hoge
{
    [System.Serializable]
    class SoundPlayer{
        [SerializeField]
        AudioClip hogehoge;
~~中略

After

SoundProvider.cs
namespace Hoge
{
    [System.Serializable]
    class SoundProvider{
        [SerializeField]
        AudioClip hogehoge;
~~中略

原因

正直よく分からないですが、元々System.Media名前空間にSoundPlayerというクラスがあります。

SoundPlayer クラス (System.Media) | Microsoft Docs
https://docs.microsoft.com/ja-jp/dotnet/api/system.media.soundplayer?view=netframework-4.8

おそらくですが[Serializable]ではない同名のクラスが存在するとこのエラーが発生すると考えられます。apk起動時にシーン内のオブジェクトに紐付いたHoge名前空間のSoundPlayerをSystem.Media名前空間内のSoundPlayerでデシリアライズしようとしているとか。

おまけ:解決に至るまで試したこと

上記のエラーをググるとエディタ関係のエラーで参照が切れていると発生するといった記事が出てきます。
が、検索結果に出てきた「LibraryとTempフォルダを削除」、「Reinportする」をしてビルドしても症状は変わらず。

この時点で完全に手詰まりで頭を抱えました。
Previewの「2D Pixel Perfect」パッケージを外してみたりしたものの改善せず。
音を付ける前は起動できていたので最近追加した音周りが怪しいということで音周りのコードを確認しました。
で、SoundPlayerという名前のクラスが別にあったので、自作のSoundPlayerの方の名前を変えたら解決しました。

当てずっぽうだったので、あんまり褒められたものではありませんが、なんとか前に進むことができました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む