Android StudioのActivityTemplate全部試してみた!(2/2)
前回に引き続き、AndroidStudioのActivityのサンプルを試してみます。 残り6個!
目次
- LoginActivity
- Master/Detail Flow
- TabbedActivity
- SettingsActivity
- ScrollingActivity
- NavigationDrawer
LoginActivity
特徴
- 一般的なログインフォーム
- 補完機能などUIが充実
Source
xml
<!-- プログレスバー --> <ProgressBar android:id="@+id/login_progress" style="?android:attr/progressBarStyleLarge" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:visibility="gone" />
<!-- メールアドレスの補完機能がついたTextView --> <AutoCompleteTextView android:id="@+id/email" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/prompt_email" android:inputType="textEmailAddress" android:maxLines="1" android:singleLine="true" />
自動補完用のTextViewなんてあったんですね。
Activity
長いのでメソッド名だけ、
- permission処理
- mayRequestContacts()
- onRequestPermissionResult(Int, Array
, IntArray)
- 連絡帳アクセス
- onCreateLoader(Int, Bundle?)
- onLoadFinished(Loader>Cursor>, Cursor)
- textViewに補完
- addEmailsToAutoComplete(List
)
- addEmailsToAutoComplete(List
- 通信処理
- inner class UserLoginTask
- プログレスダイアログ
- showProgress(Boolean)
機能だけでなく、UX的な面でも参考になります……!
Master/Detail Flow
特徴
Source
基本は一般的なActivityとFragmentです。特徴的なのは、出し分けのロジックですね。
今回はこれを2つのitem_list.xml
の呼び分けで実現しています。app/src/main/res/layout
にあるものが呼ばれていればスマホサイズ、app/src/main/res/layout-w900dp
のものが呼ばれていればタブレットサイズとなります。
Activity
if (item_detail_container != null) { // The detail container view will be present only in the // large-screen layouts (res/values-w900dp). // If this view is present, then the // activity should be in two-pane mode. twoPane = true }
onClickListener = View.OnClickListener { v -> val item = v.tag as DummyContent.DummyItem // タブレットかどうか if (twoPane) { val fragment = ItemDetailFragment().apply { arguments = Bundle().apply { putString(ItemDetailFragment.ARG_ITEM_ID, item.id) } } parentActivity.supportFragmentManager .beginTransaction() .replace(R.id.item_detail_container, fragment) .commit() } else { val intent = Intent(v.context, ItemDetailActivity::class.java).apply { putExtra(ItemDetailFragment.ARG_ITEM_ID, item.id) } v.context.startActivity(intent) } }
TabbedActivity
特徴
3種類のスタイルがあります。
- Swipe Views
- Action Bar Tabs
- Action Bar Spinner
が、、、全体的に上手くいきませんでした。
SwipeViewsはデザインが違い、その他はそもそもビルドが出来ませんでした。
AndroidStudioのバージョンにもよるのでしょうが、別途自分で書いてみます。
SettingsActivity
特徴
アプリの設定画面を生成してくれます。
アプリ内で完結したいものは、OSの設定画面へと遷移してくれます。
Source
AppCompatPreferenceAcrivityでPreferenceごとにFragmentを定義しています。
@TargetApi(Build.VERSION_CODES.HONEYCOMB) class GeneralPreferenceFragment : PreferenceFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) addPreferencesFromResource(R.xml.pref_general) setHasOptionsMenu(true) // Bind the summaries of EditText/List/Dialog/Ringtone preferences // to their values. When their values change, their summaries are // updated to reflect the new value, per the Android Design // guidelines. bindPreferenceSummaryToValue(findPreference("example_text")) bindPreferenceSummaryToValue(findPreference("example_list")) } override fun onOptionsItemSelected(item: MenuItem): Boolean { val id = item.itemId if (id == android.R.id.home) { startActivity(Intent(activity, SettingsActivity::class.java)) return true } return super.onOptionsItemSelected(item) } }
の繰り返しです。
また、xml/pref_xxx
でpreferenceごとの画面を定義しています。
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to dismiss it. --> <!-- NOTE: ListPreference's summary should be set to its value by the activity code. --> <ListPreference android:defaultValue="180" android:entries="@array/pref_sync_frequency_titles" android:entryValues="@array/pref_sync_frequency_values" android:key="sync_frequency" android:negativeButtonText="@null" android:positiveButtonText="@null" android:title="@string/pref_title_sync_frequency" /> <!-- This preference simply launches an intent when selected. Use this UI sparingly, per design guidelines. --> <Preference android:title="@string/pref_title_system_sync_settings"> <intent android:action="android.settings.SYNC_SETTINGS" /> </Preference> </PreferenceScreen>
こうやって使うんですね。
ScrollingActivity
特徴
- CollapsingToolbarLayoutの中に配置したAppBarの大きさが可変になる
Source
そのままじゃ上手く表示されなかったのでちょっと修正……
以前紹介した本を参考にしました。
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:id="@+id/appbar" android:layout_width="match_parent" android:layout_height="180dp" android:elevation="10dp"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/toolbarLayout" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="@color/colorPrimary" app:layout_collapseMode="pin" /> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ScrollingActivity" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_mar[f:id:t-miliya612:20180510205238p:plain]gin="@dimen/text_margin" android:text="@string/large_text" /> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout>
app:layout_behavior="@string/appbar_scrolling_view_behavior"
を忘れるとコンテントの文字が被ってしまうので要注意です。
NavigationDrawer
特徴
- 左側から出てくるタイプのナビゲーション
Source
設定は結構簡単でした。
Activity
val toggle = ActionBarDrawerToggle( this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) drawer_layout.addDrawerListener(toggle) toggle.syncState() nav_view.setNavigationItemSelectedListener(this)
activity_navigation_drawer.xml
... <android.support.design.widget.NavigationView android:id="@+id/nav_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" app:headerLayout="@layout/nav_header_navigation_drawer" app:menu="@menu/activity_navigation_drawer_drawer" /> ...
layout/nav_header_navigation_drawer.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="@dimen/nav_header_height" android:background="@drawable/side_nav_bar" android:gravity="bottom" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:theme="@style/ThemeOverlay.AppCompat.Dark"> <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/nav_header_desc" android:paddingTop="@dimen/nav_header_vertical_spacing" app:srcCompat="@mipmap/ic_launcher_round" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="@dimen/nav_header_vertical_spacing" android:text="@string/nav_header_title" android:textAppearance="@style/TextAppearance.AppCompat.Body1" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/nav_header_subtitle" /> </LinearLayout>
menu/activity_navigation_drawer_drawer.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:showIn="navigation_view"> <group android:checkableBehavior="single"> <item android:id="@+id/nav_camera" android:icon="@drawable/ic_menu_camera" android:title="Import" /> <item android:id="@+id/nav_gallery" android:icon="@drawable/ic_menu_gallery" android:title="Gallery" /> <item android:id="@+id/nav_slideshow" android:icon="@drawable/ic_menu_slideshow" android:title="Slideshow" /> <item android:id="@+id/nav_manage" android:icon="@drawable/ic_menu_manage" android:title="Tools" /> </group> <item android:title="Communicate"> <menu> <item android:id="@+id/nav_share" android:icon="@drawable/ic_menu_share" android:title="Share" /> <item android:id="@+id/nav_send" android:icon="@drawable/ic_menu_send" android:title="Send" /> </menu> </item> </menu>
まとめ
全体的に学習材料として面白かったです。ただ、Tab系のActivityは謎でした……結構使いそうな気がするんだけどなぁ。
初めて実装する際には、手直しのことも考えるとEmptyActivityで頑張った方がよさそうですね。
そして、 初めから全部で2記事って決めると大変つらいことになる という学びを得ました。以上です。
Android StudioのActivityTemplate全部試してみた!(1/2)
AndroidStudioでActivityを追加するとき、とりあえず何も考えずにEmptyActivityを選択していました。
これ他の選ぶとどうなってるんだろう、とふと気になってしまい、、、
とりあえず全部遊んでみることにしました。
リポジトリはこちら
目次(1/2)
- Basic Activity
- BottomNavigationActivity
- FullscreenActivity
- GoogleAdMobAdsActivity
- GoogleMapsActivity
- LoginActivity
Basic Activity
特徴
- Toolbarが設定されている
- FloatActionButtonが配置されている
onClickListener
でToastを表示
content_{ActivityName}
の名前で空のConstraintLayoutがincludeされている
特に難しいものはないですね。ToolbarやFABを生成してくれるのは使い勝手が良さそうです。
BottomNavigationActivity
- BottomNavigationの配置
- 遷移するとtextが変更
- 選択したアイコンは色が変わってサイズが少し大きくなる
res/menu
を使用したデータ受け渡し (あれ、menuなんて使ったことなかった……これめっちゃ便利……)
FullscreenActivity
- Fullscreen表示に対応
- タップしたらFullscreenの解除、設定のトグル
面白そうなのでソースも追っていきます。分解して見ていきましょう
Source
- onCreate()
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_fullscreen) supportActionBar?.setDisplayHomeAsUpEnabled(true) mVisible = true // Set up the user interaction to manually show or hide the system UI. fullscreen_content.setOnClickListener { toggle() } // Upon interacting with UI controls, delay any scheduled hide() // operations to prevent the jarring behavior of controls going away // while interacting with the UI. dummy_button.setOnTouchListener(mDelayHideTouchListener) }
- toggle()
private fun toggle() { if (mVisible) { hide() } else { show() } }
- hide()
private fun hide() { // Hide UI first supportActionBar?.hide() fullscreen_content_controls.visibility = View.GONE mVisible = false // Schedule a runnable to remove the status and navigation bar after a delay mHideHandler.removeCallbacks(mShowPart2Runnable) mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY.toLong()) }
- mShowPart2Runnable
private val mShowPart2Runnable = Runnable { // Delayed display of UI elements supportActionBar?.show() fullscreen_content_controls.visibility = View.VISIBLE }
- mHidePart2Runnable
private val mHidePart2Runnable = Runnable { // Delayed removal of status and navigation bar // Note that some of these constants are new as of API 16 (Jelly Bean) // and API 19 (KitKat). It is safe to use them, as they are inlined // at compile-time and do nothing on earlier devices. fullscreen_content.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION }
systemUiVisibility
については、こちらの記事が詳しいです。Android4.xの時期、ハードの物理ボタンが無くなった時期に生まれたんですね。Xperia acroとか懐かしい……。
GoogleAdMobAdsActivity
特徴
- GoogleのAdMobを使用する
- Interstitial(全画面)タイプの広告、Bannerタイプの広告の2種類がある
Source
Interstitialの場合もBannerの場合も、呼んでるAPIは変わらないみたいです。以下、Interstitialの例です。
build.gradle
dependencies { implementation 'com.google.android.gms:play-services-ads:15.0.1'
Activity
// Create the InterstitialAd and set the adUnitId (defined in values/strings.xml). interstitialAd = newInterstitialAd() loadInterstitial() ... private fun newInterstitialAd(): InterstitialAd { return InterstitialAd(this).apply { adUnitId = getString(R.string.interstitial_ad_unit_id) adListener = object : AdListener() { override fun onAdLoaded() { next_level_button.isEnabled = true } override fun onAdFailedToLoad(errorCode: Int) { next_level_button.isEnabled = true } override fun onAdClosed() { // Proceed to the next level. goToNextLevel() } } } } private fun showInterstitial() { // Show the ad if it's ready. Otherwise toast and reload the ad. if (interstitialAd?.isLoaded == true) { interstitialAd?.show() } else { Toast.makeText(this, "Ad did not load", Toast.LENGTH_SHORT).show() goToNextLevel() } } private fun loadInterstitial() { // Disable the next level button and load the ad. next_level_button.isEnabled = false val adRequest = AdRequest.Builder() .setRequestAgent("android_studio:ad_template") .build() interstitialAd?.loadAd(adRequest) } ...
基本的な流れとしては、
InterstitialAd(this).apply
でInterstitialの広告を生成AdRequest.Builder()
で広告リクエストを作成interstitialAd?.loadAd(adRequest)
で実際に広告をリクエスト
となっているようです。
GoogleMapsActivity
特徴
- GoogleMapを表示
- 事前にgoogle developer consoleでGoogle MapsのAPI keyの取得が必須
res/values/google_maps_api.xml
を参照
Source
コードはそんなに複雑ではありません。
build.gradle
dependencies { implementation 'com.google.android.gms:play-services-maps:15.0.1'
Activity
import android.support.v7.app.AppCompatActivity import android.os.Bundle import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.OnMapReadyCallback import com.google.android.gms.maps.SupportMapFragment import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MarkerOptions class MapsActivity : AppCompatActivity(), OnMapReadyCallback { private lateinit var mMap: GoogleMap override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_maps) // Obtain the SupportMapFragment and get notified when the map is ready to be used. val mapFragment = supportFragmentManager .findFragmentById(R.id.map) as SupportMapFragment mapFragment.getMapAsync(this) } /** * Manipulates the map once available. * This callback is triggered when the map is ready to be used. * This is where we can add markers or lines, add listeners or move the camera. In this case, * we just add a marker near Sydney, Australia. * If Google Play services is not installed on the device, the user will be prompted to install * it inside the SupportMapFragment. This method will only be triggered once the user has * installed Google Play services and returned to the app. */ override fun onMapReady(googleMap: GoogleMap) { mMap = googleMap // Add a marker in Sydney and move the camera val sydney = LatLng(-34.0, 151.0) mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney")) mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney)) } }
一旦今回はここまで!
残り6つはまた次回。。。
kotlin-stdlib-jreがdeprecatedになっていたので深掘りしてみた
環境
- AndroidStudio 3.1.2
- Kotlin 1.2.41
起こったこと
AndroidStudioで新しくプロジェクトを作ってみたら、{project}/app.gradle
のorg.jetbrains.kotlin:kotlin-stdlib-jre7
がdeprecatedになっていました。
kotlin-stdlib-jre7 is deprecated since 1.2.0 and should be replaced with kotlin-stdlib-jdk7
言われた通りjre-7
からjdk-7
に変えたら直りました。よかったです。
……だけで終わるのもアレなので、しっかり調べてみました。
kotlin-stdlibについて
まさにStandardなlibraryで、Kotlinのコア部分の実装を担っています。
The Kotlin Standard Library provides living essentials for everyday work with Kotlin. These include:
- Higher-order functions implementing idiomatic patterns (let, apply, use, synchronized, etc).
- Extension functions providing querying operations for collections (eager) and sequences (lazy).
- Various utilities for working with strings and char sequences.
- Extensions for JDK classes making it convenient to work with files, IO, and threading.
kotlin-stdlib
はコア部分、kotlin-stdlib-jre/jdk
はextension的な部分っぽいですね。
似たような議論がこちらに。
変更理由
Java9、特にその中でもモジュールシステムのサポートのためみたいです。
The Kotlin standard library is now fully compatible with the Java 9 module system, which forbids split packages (multiple jar files declaring classes in the same package). In order to support that, new artifacts kotlin-stdlib-jdk7 and kotlin-stdlib-jdk8 are introduced, which replace the old kotlin-stdlib-jre7 and kotlin-stdlib-jre8.
The declarations in the new artifacts are visible under the same package names from the Kotlin point of view, but have different package names for Java. Therefore, switching to the new artifacts will not require any changes to your source code.
モジュールシステムについて
- 別名 Project Jigsaw
- Packageの上位概念としてモジュールを作る
実際にKotlin内のコードでも使われていますね。
目的
- 構成の信頼性の向上
- モジュール間の依存性を明示的に宣言
- コンパイル時、実行時に依存性の認識が可能
- 強力なカプセル化
- モジュールは明示的にエクスポートしない限り外部からのアクセス不可
- エクスポートは全開放するか特定のモジュールにのみ解放するか選べる
- スケーラブルなJavaプラットフォーム
- プラットフォームがモジュールで分割される
- 必要なものだけを集めたカスタムランタイムを作れる
- ランタイムのサイズを削減できる
- プラットフォームの整合性向上
- パフォーマンスの向上
- これちょっとわかりませんでした。。。
http://www.oracle.com/webfolder/technetwork/jp/javamagazine/Java-SO17-Modules.pdf
エクスポートを明示的にできるのは良さそうですね!open
が必要なのはKotlinの影響もあるんでしょうか。
所感
Android-Java-Kotlinと変数が増えると最新版を追っかけるのは大変ですね……。
モジュールシステムに関しては、gradleを使ったモジュールとの今後が気になります。試しにこれ使ってマルチモジュールのアプリ書いてみたいなぁ(伏線)
また、stdlibに関して、ネーミングの理由はよくわからないままでした。何故拡張版をjreなんてネーミングにしたんでしょう。
おまけ
AndroidのJava7サポートって完全じゃなかったようなーと思い調べてみた所、こんな記事が見つかりました。
Androidの開発環境における「Javaのバージョン」はいくつかの考え方があり、自明ではありません。ここでは3つに分けて考えます。
ひとつはAndroid端末にインストールされているAndroid OS、それにバンドルされているDalvik処理系(仮想マシン)がどのJVMバージョンに相当するかというものです。
もうひとつは、Android端末にインストールされているAndroid OS、それにバンドルされているJava標準ライブラリのバージョンです。
あれ、そしたら結局AndroidってどのJava使えるの……?
Java 8 言語機能の使用 | Android Developers
Android Studio では、特定の Java 8 言語機能とそれらを使用するサードパーティ ライブラリのビルトイン サポートを提供しています。
し、知らなかった……。
こちらからは以上です。
公式のcodelabでFlutterに入門してみた
今度Flutter Meetup Tokyo #2に参加するので、事前にちょっと手を動かしてみました。
What’s Flutter
Flutter - Beautiful native apps in record time
Googleが開発しているmobile appのSDKで、Dartという言語を用いて開発します。
本家サイトによると、特徴は以下の5つ。
- Fast development
- ホットリロードが可能
- Expressive and Flexible UI
- Modern, reactive framework
- Access native features and SDKs
- Unified app development
環境構築
環境
- macOS 10.13.4
- Android Studio 3.1.2
- Xcode 9.3
1. install Dart
$ brew tap dart-lang/dart
==> Tapping dart-lang/dart Cloning into '/usr/local/Homebrew/Library/Taps/dart-lang/homebrew-dart'... remote: Counting objects: 5, done. remote: Compressing objects: 100% (5/5), done. remote: Total 5 (delta 0), reused 2 (delta 0), pack-reused 0 Unpacking objects: 100% (5/5), done. Tapped 1 formula (30 files, 26.3KB)
$ brew install dart --with-dartium --with-content-shell
==> Installing dart from dart-lang/dart ==> Downloading https://storage.googleapis.com/dart-archive/channels/stable/release/1.24.3/sdk/dartsdk-macos-x64-release.zip ######################################################################## 100.0% ==> Downloading https://storage.googleapis.com/dart-archive/channels/stable/release/1.24.3/dartium/dartium-macos-x64-release.zip ######################################################################## 100.0% ==> Downloading https://storage.googleapis.com/dart-archive/channels/stable/release/1.24.3/dartium/content_shell-macos-x64-relea ######################################################################## 100.0% ==> Caveats Please note the path to the Dart SDK: /usr/local/opt/dart/libexec ==> Summary 🍺 /usr/local/Cellar/dart/1.24.3: 3,039 files, 657.7MB, built in 21 seconds
2. install Flutter
$ git clone https://github.com/flutter/flutter.git
Cloning into 'flutter'... remote: Counting objects: 120517, done. remote: Total 120517 (delta 0), reused 0 (delta 0), pack-reused 120516 Receiving objects: 100% (120517/120517), 36.60 MiB | 3.11 MiB/s, done. Resolving deltas: 100% (90418/90418), done.
$ git co v0.3.1 # flutter公式ページのzip最新版がv0.3.1だったため(2018/04/29現在)
$ vim ~/.zprofile
export PATH=$HOME/flutter/bin:$PATH
3. Set up environment
$ flutter doctor
Downloading Dart SDK from Flutter engine 09d05a38912a3c1a906e95099cac9a7e14fae85f... % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 43.9M 100 43.9M 0 0 9507k 0 0:00:04 0:00:04 --:--:-- 9508k Building flutter tool... ╔════════════════════════════════════════════════════════════════════════════╗ ║ Welcome to Flutter! - https://flutter.io ║ ║ ║ ║ The Flutter tool anonymously reports feature usage statistics and crash ║ ║ reports to Google in order to help Google contribute improvements to ║ ║ Flutter over time. ║ ║ ║ ║ Read about data we send with crash reports: ║ ║ https://github.com/flutter/flutter/wiki/Flutter-CLI-crash-reporting ║ ║ ║ ║ See Google's privacy policy: ║ ║ https://www.google.com/intl/en/policies/privacy/ ║ ║ ║ ║ Use "flutter config --no-analytics" to disable analytics and crash ║ ║ reporting. ║ ╚════════════════════════════════════════════════════════════════════════════╝ Downloading Material fonts... 0.9s Downloading package sky_engine... 0.5s Downloading common tools... 1.1s Downloading darwin-x64 tools... 1.9s Downloading android-arm-profile/darwin-x64 tools... 0.7s Downloading android-arm-release/darwin-x64 tools... 0.8s Downloading android-arm64-profile/darwin-x64 tools... 0.8s Downloading android-arm64-release/darwin-x64 tools... 0.8s Downloading android-x86 tools... 1.8s Downloading android-x64 tools... 1.5s Downloading android-arm tools... 1.6s Downloading android-arm-profile tools... 1.0s Downloading android-arm-release tools... 1.2s Downloading android-arm64 tools... 1.3s Downloading android-arm64-profile tools... 1.1s Downloading android-arm64-release tools... 1.0s Downloading ios tools... 2.2s Downloading ios-profile tools... 2.1s Downloading ios-release tools... 1.9s Downloading Gradle Wrapper... 0.2s Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel unknown, v0.3.1, on Mac OS X 10.13.4 17E199, locale ja-JP) -Error executing simctl: -6 dyld: Symbol not found: _SimDeviceBootKeyDisabledJobs Referenced from: /Applications/Xcode.app/Contents/Developer/usr/bin/simctl Expected in: /Library/Developer/PrivateFrameworks/CoreSimulator.framework/Versions/A/CoreSimulator in /Applications/Xcode.app/Contents/Developer/usr/bin/simctl [!] Android toolchain - develop for Android devices (Android SDK 27.0.3) ! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses [!] iOS toolchain - develop for iOS devices (Xcode 9.3) ✗ Xcode requires additional components to be installed in order to run. Launch Xcode and install additional required components when prompted. ✗ libimobiledevice and ideviceinstaller are not installed. To install, run: brew install --HEAD libimobiledevice brew install ideviceinstaller ✗ ios-deploy not installed. To install: brew install ios-deploy ✗ CocoaPods not installed. CocoaPods is used to retrieve the iOS platform side's plugin code that responds to your plugin usage on the Dart side. Without resolving iOS dependencies with CocoaPods, plugins will not work on iOS. For more info, see https://flutter.io/platform-plugins To install: brew install cocoapods pod setup [✓] Android Studio (version 3.1) ✗ Flutter plugin not installed; this adds Flutter specific functionality. ✗ Dart plugin not installed; this adds Dart specific functionality. [!] IntelliJ IDEA Community Edition (version 2017.2.6) ✗ Flutter plugin not installed; this adds Flutter specific functionality. ✗ Dart plugin not installed; this adds Dart specific functionality. [✓] VS Code (version 1.22.2) [!] Connected devices ! No devices available ! Doctor found issues in 4 categories.
いくつか引っかかりました。
Android license
$ flutter doctor --android-licenses
3 of 6 SDK package licenses not accepted. 100% Computing updates... Review licenses that have not been accepted (y/N)? y 1/3: License android-googletv-license: 2/3: License google-gdk-license: 3/3: License mips-android-sysimage-license:
iOS
CocoaPods入れてなかったのがつらい。。。30分休み。
Android Studio
- Flutter plugin
- Dart plugin
Device
Device繋がってないよ!
はい。
対応し、再挑戦してみます。
$ flutter doctor
master Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel unknown, v0.3.1, on Mac OS X 10.13.4 17E199, locale ja-JP) [✓] Android toolchain - develop for Android devices (Android SDK 27.0.3) [✓] iOS toolchain - develop for iOS devices (Xcode 9.3) [✓] Android Studio (version 3.1) [✓] VS Code (version 1.22.2) [✓] Connected devices (2 available)
いい感じです。これで開発環境が整いました。
codelabについて
スタートアップの会社名候補を生成するシンプルなモバイルアプリ
を作っていきます。お気に入り機能があり、別のページでお気に入りの名前リストを表示することもできます。
開発はAndroidStudioを使いましたが、VScodeなどを使っても良いそうです。
Step 1: Create the starting Flutter app
Flutterのプロジェクトを新規生成します。
Android Studioを使う場合はウィザードから、VScodeを使う場合はコマンドからアプリの雛形を生成できます。
ファイル構成はこんな感じです。
├── README.md ├── android # androidネイティブのコード ├── build ├── ios # iOSネイティブのコード ├── lib │ └── main.dart # 手を加えていくファイル ├── pubspec.lock ├── pubspec.yaml # 依存関係などなどを管理するファイル └── test └── widget_test.dart
終わったら、codelabにあるコードでlib/main.dart
を置き換えましょう。
Step 2: Use an external package
english_wordsというプラグインを入れます。プラグインの追加は、以下の手順で行います。
pubspec.yaml
に追記english_words: ^3.1.0
- Android Studioのタブに表示されている
Packages Get
をクリック- 以下が表示されればOK
${PATH_TO_FLUTTER}/flutter/bin/flutter --no-color packages get Running "flutter packages get" in ${YOUR_APP_NAME}... Process finished with exit code 0
lib/main.dart
にimportを追記import 'package:english_words/english_words.dart';
後はいつも通り使うだけです。特に難しいところはなさそうです。
Step 3: Add a Stateful widget
WidgetにはStatefulとStatelessの2種類があります。画面を表示している間にWidgetを書き換えるかどうかで使い分けます。今回のアプリでは無限スクロールのListを描画するので、StatefulWidgetを拡張したクラスを使用します。
まずは中央に表示されたtextを試しにstatefulにしてみます。
import 'package:flutter/material.dart'; import 'package:english_words/english_words.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Welcome to Flutter', home: new Scaffold( appBar: new AppBar( title: new Text('Welcome to Flutter'), ), body: new Center( child: new RandomWords(), ), ), ); } } class RandomWords extends StatefulWidget { @override createState() => new RandomWordsState(); } class RandomWordsState extends State<RandomWords> { @override Widget build(BuildContext context) { final wordPair = new WordPair.random(); return new Text(wordPair.asPascalCase); } }
Step 4: Create an infinite scrolling ListView
いよいよ無限スクロールのListViewを生成します。ListView.builder()
を返すWidgetを定義し、builderの引数として渡すitemBuilder
プロパティの中でリスト生成のロジックを記述します。
(省略...) class RandomWordsState extends State<RandomWords> { final _suggestions = <WordPair>[]; final _biggerFont = const TextStyle(fontSize: 18.0); @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Startup Name Generator'), ), body: _buildSuggestions(), ); } Widget _buildSuggestions() { return new ListView.builder( // ListView.builderを生成 padding: const EdgeInsets.all(16.0), itemBuilder: (context, i){ // itemBuilderのプロパティにリストを描画する無名関数を定義 if (i.isOdd) return new Divider(); final index = i ~/ 2; if (index >= _suggestions.length) { _suggestions.addAll(generateWordPairs().take(10)); } return _buildRow(_suggestions[index]); }, ); } // リスト1行をセットする Widget _buildRow(WordPair pair) { return new ListTile( title: new Text( pair.asPascalCase, style: _biggerFont, ), ); } }
Step 5: Add interactivity
ハート型アイコンをセットして、タップするとお気に入り登録できるようにします。Icons
クラスがすごく充実していて、アイコン追加がかなり楽そうです。
Androidで言うonClick
メソッドは、onTap
プロパティに対応します。
(省略...) Widget _buildRow(WordPair pair) { final alreadySaved = _saved.contains(pair); return new ListTile( title: new Text( pair.asPascalCase, style: _biggerFont, ), trailing: new Icon( alreadySaved ? Icons.favorite : Icons.favorite_border, color: alreadySaved ? Colors.red : null, ), onTap: () { setState(() { if (alreadySaved) { _saved.remove(pair); } else { _saved.add(pair); } }); }, ); }
Step 6: Navigate to a new screen
画面遷移のコードを書いていきます。Flutterでは新しい画面のことをroute
と言うみたいですね。
route
のスタックは、Navigation
クラスを使って管理していきます。
(省略...) void _pushSaved(){ Navigator.of(context).push( new MaterialPageRoute( builder: (context) { // お気に入り登録されたリストを各行にマップ final tiles = _saved.map( (pair) { return new ListTile( title: new Text( pair.asPascalCase, style: _biggerFont, ), ); }, ); // リストを生成 final divided = ListTile .divideTiles( context: context, tiles: tiles, ) .toList(); // appBarと画面のコンテンツ(リスト)をセット return new Scaffold( appBar: new AppBar( title: new Text('Saved Suggestions'), ), body: new ListView(children: divided), ); }, ), ); }
Step 7: Change the UI using Themes
UIの色をいじってみます。今回はひとまず、primaryColorを変更してappBarの色を変えてみます。
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Startup Name Generator', theme: new ThemeData( primaryColor: Colors.white, ), home: new RandomWords(), ); } }
簡単ですね!ただ、結構大胆にカスタマイズをしようとすると大変なことになりそうな。。。
MaterialDesignからどうしても離れなきゃいけないときは厳しそうですね。
作ったもの
まとめ
一通りアプリを完成させるところまでやってみました。かなり簡単にそれなりのUIを作成することができました。
イメージとしてはAnkoのような感じかなーと思っていたのですが、ちょっと違う印象でした。ReactやVueなどのライブラリは使ったことがないのですが、そういったjsのライブラリで培ったノウハウが必要になりそうです。
とはいえ、これでFlutterのことが何となく完全に理解できました。今からmeetupが楽しみです。
今後やりたいこと
- ファイル構成を考える
- ファイルってどうやって分けていくんだろ
- いい加減Reactやらねば。。。
- OS依存の機能を使う
- network通信
- GPS
- camera
- SMS(出来るかな……?)
#技術書典 に行ってきたからまずは買ったものをまとめておく
昨日、技術書典4に行ってきました。前回に引き続き2回目の参加です。
今回は晴れだったこともあり、恐ろしいほどの参加者数に。
twitter.comただいまを持ちまして、 #技術書典4 閉幕しました!今回の総参加者数は6380人でした。
— 技術書典公式アカウント (@techbookfest) 2018年4月22日
皆さんご来場ありがとうございました。
だいぶ気合いの入った入場待ちの列に感化されて、合計12冊の収穫を得ました。熱かったししょうがないです。
もちろんまだ読み終わってないのですが、一旦出会い等をまとめておきます。
目次
- DNSをはじめよう ~基礎からトラブルシューティングまで~
- Now and Future
- SVGで作ろうローディングアニメーション
- こうしてぼくらは、書籍を売るアプリを作った
- こうしてぼくらは、書籍を売るアプリを作った2.0.1
- Gopherの休日
- 大きめのAndroidアプリでの設計を考えてみる〜pocket〜
- 神Excelで働き方改革(仮)
- Vue.jsとFirebaseで作るミニWEBサービス 初めてのサーバーレスシングルアプリケーション
- ざっクリわかる Ubuntu 18.04 LTS
- Nature Design with Houdini 自然界のアルゴリズムを利用したデザインレシピ集
- Android Things with Kotlin Hands-on Book
DNSをはじめよう ~基礎からトラブルシューティングまで~
気になった理由
- 学生時代、wordpressに独自ドメインを当てるときまさに「数時間かかるんだよねー」とか知ったかぶりをしてた
- 会社入れば研修とかでやるじゃろ、って思ってたらあんまやらなかった
- 今後自分でサービスを立ち上げたりするときにアワアワしたくない
- ドリル付き←最高
- 半透明の遊び紙で製本してあり、本自体のクオリティが高い
Now and Future
気になった理由
第1章 Android通知最前線 What's new in Android P
- 普段Androidの開発をする上で、新しいOSの機能ってシェアの都合上すぐに触ることがない(アプリに求められるのはある程度色々なデバイスで動くこと)
- 何だかんだ理由をつけて調べてなかったが、Twitterで情報収集するようにしてから気になって仕方がない
第2章 Fit API for Androidを使ってみよう
第7章 Androidライブラリ実践入門
SVGで作ろうローディングアニメーション
気になった理由
こうしてぼくらは、書籍を売るアプリを作った
気になった理由
- 目当ての章
- 第3章 今日から始めるChatOpsの勘どころ
- 第4章 Debug Menuはじめました。
- 第5章 実践Go言語のインタフェース
- 特に第4章の学びが濃そう
- debugメニュー、「絶対触っちゃいけないゾーン」になってしまったり
- productionを想定してちゃんと管理しているのが流石
こうしてぼくらは、書籍を売るアプリを作った2.0.1
気になった理由
- 目当ての章
- 第2章 Google App EngineでGoのバージョンアップを行う
- 第4章 キンパツは企画を考え、どんなことをしてきたのか
- キンパツこと@operandosさんの話は特にきになる
- ようやくキャリア周りの話に敏感になるようになってきた
Gopherの休日
気になった理由
- お世話になっているgolang.tokyoの頒布物
- 表紙が最高
- @tenntennさんが売り子してるのすごい
大きめのAndroidアプリでの設計を考えてみる〜pocket〜
気になった理由
clean architectureAndroidアプリ設計の記事でお世話になっているkgmyshinさん(思い込みでclean architectureだと決めつけていましたが、記事中に指摘はありませんでした。訂正します。2018/04/23 19:49)- ただ設計だけでなくマルチモジュールでの開発
- 以前yahooのbonfireでもマルチモジュールの話は聞いたけど、実例が読めるのは有難い
- Bonfire Android #3 - connpass
- 取り置き制度は神
神Excelで働き方改革(仮)
気になっていること
- 自動化自動化と叫ばれているだけあって、いわゆる神Excelはほとんど姿を見かけない(見かけたことがない)
- ただ、たまに他の人のスプレッドシートを見ると「オッ」となることが
- 我々は神Excelの悲劇を忘れてはならない
- (正直何が出来て何がつらいのかよくわかってないので、雰囲気でしか喋れない)
Vue.jsとFirebaseで作るミニWEBサービス 初めてのサーバーレスシングルアプリケーション
作者様はこちら nabettu.hatenablog.com
気になった理由
- SPA作ったことない!
- Vue.jsもいい加減触りたい!
- しっかりWebサービス立てるのも大事だけど、Firebase等使ってライトにサクッと立てる技術も大事だと思った
ざっクリわかる Ubuntu 18.04 LTS
気になった理由
- これ学生時代に欲しかったなぁとの思いから
- 大学の授業や先輩の宗教上の理由から、やたらとCentじゃなくてUbuntuを触る機会が多かった
- やっぱり初めに触れるなら、CUIだけのサーバー用途よりもGUIを含めたデスクトップ環境の方が楽でいい
- 「ざっクリ」というだけあってそんなに濃い内容が詰まってるわけではないけれど、聞かれたときにスッと出したい本
Nature Design with Houdini 自然界のアルゴリズムを利用したデザインレシピ集
気になった理由
- これぞ技術書典に来た理由
- webとかAndroidとか、普段の業務や趣味の領域じゃ出会わない分野と出くわすことができる
- アルゴリズミックデザインの本
アルゴリズムを用いて形状のデザインをする」
自分の手入力により形を作り上げていくデータが重くなりがちな作り方ではなく、数理的なルールを記述するだけで少ないデータで複雑な形状を作ることができる
- SVGと繋がる点がありそう
- 対象読者は、アルゴリズミックデザインについて全く知らない人、ではないので、ちょっとずつ勉強しながら読んでいきたい
Android Things with Kotlin Hands-on Book
気になった理由
- 今のところAndroidアプリ開発者としてお仕事をしている
- PWAなどが流行っている中、今やっているネイティブアプリの知識って今後どこまで通用するのかちょっと不安になる
- この本のことを知ってからAndroid Thingsのことも知ったんだけど、こういった道で自分の経験を活かしていけるのであれば……という気持ち。
以上です! (長かった……)
GWに向けて、最高の遊び道具が揃った感じですね!やっていきましょう!
ちなみに
買ったものはTrelloでまとめてみるトライアルをしてみています。
Go Conference 2018 Springに参加してきた感動をなんとか言葉にしたい
1週間経ってしまいましたが、Go Conference2018 Springに参加してきました。やっていくぞという気持ちの昂ぶりが止まらないためちょっと書き残しておきます。
資料やセッション等はconnpassのページをどうぞ。
この1週間、会社で「GoCon行ってきた!スライド見て!ほら!」と騒いでおりました。「GoCon良かったなぁ」といい思い出で終わらせないうちに、特に面白かったものをまとめておきます。
Testing with microservices in merpay
概要
- merpayで使っているgRPCとGoの話
- 決済システムを扱う上でのテストについて
- マイクロサービスアーキテクチャを使用する上で、一貫性をどう保っていくか
感想
Go Conferenceでスカラーシップを提供してくれたmercari, merpayのスポンサー枠セッションです。
今業務でマイクロサービス(的なもの)のTransactionalな処理についてちょうど考えていたところだったので、いいタイミングで発表を聞けました!実際、コンポーネントとその接点が増えれば増えるほどテスト項目は増えるので、そこを完璧にテストするのって難しいよなぁ……などと思っていいたのですが、やらなきゃ駄目ですよねごめんなさい。
特に、gRPCに関するテストの話題ということで、主題とは外れますが「ああ、gRPCってもうこんなレベルで動いているんだ」などと今更ながら感慨にふけっていました。学習コストも踏まえると、すぐにチームで導入!というのは正直難しい点もあります。だからといって知らないでいい、というのは問題外ですが、一方業務で必要に駆られる環境に身を置くことって大事だなぁと改めて感じました。業務でGo触りたいなぁ……。
How to write Go code
概要
- 対象は初学者、教える人、マネージャー
- Goを言語思想から学ぼう
- Goを学ぶ際のリソースは公式にある。活用しよう。
- https://golang.org/
- Go公式ページ
- Documentationを読む
- https://godoc.org/
- Goのライブラリのドキュメント
- https://blog.golang.org/
- Goの開発ブログ
- 言語思想はここ
- https://github.com/golang/go/wiki
- Go関連の情報まとめ
- https://go-proverbs.github.io/
- 2015年に開催されたGopherfestでの@rob_pikeのセッション「Go Proverbs」をまとめたもの
- 動画へのリンク集となっている
- https://golang.org/
感想
あるものを学ぶ際、公式のドキュメントを読んで勉強しようというのはどの言語、ライブラリでも変わらないことですよね。ただ、頭では分かっていてもGoogle検索で初心者向けの記事を探ってしまう……というのが恥ずかしながら現状でした。アーキテクチャや書き方の好みなども相まって、「公式にはこうあるけどほんとは……」みたいな情報ばかり出回ってしまったりするとなかなかつらいところです。
Goの場合、書き方がある程度統一されている点もあり、ドキュメントがある程度Godとなりえます。もちろん、発表者のkaneshinさんもコメントされていましたが、開発していく中の応用的な書き方として、個人のブログを参照することも価値があります。一方、バージョンや有用性を判断する基準を持ち合わせていない時期に急に応用面に触れてしまうと、という点もあり。
こういう勉強の仕方の部分をクローズアップしてくれる発表はめちゃくちゃ有難かったです……!「Goの命名ってどうすればいいんだっけ」とか聞かれた際にポンってURLを投げられるような場所として、覚えるくらい読み込んでおきたいです。
所感
資料も上がるのに勉強会わざわざ何で行くの、と聞かれることがあります。同じ問題を考えている人と実際に会って話せる、というのがその時の答えでした。加えて、一つ大きな理由として、自分が興味のあるテーマの中で知らなかったことの存在を知り、それについてある程度時間を取って考えられる、というものがあるなぁと思うわけです。
改めてスケジュールを全体的に眺めてみると、本当に被りのないテーマばかりだったなと思います。これだけ様々なレイヤー、領域が何か1つのテーマを持って集まれる場ってそんなに無いんじゃないかなと。
大げさに言えばGo言語の共通言語的な側面が好きです。これってコンピュータサイエンスの基礎が無い僕のような人間にとってすごく有難くて、例えばOS関連のシステムプログラミングを勉強したい、といった際、じゃあそのためにC言語を勉強しようとなると、それだけでまた一つハードルが上がってしまいます。一つの言語でアプリケーションからOSのAPIを直で触るような開発まで実際に使われているとなると、また1つ知れることが増えてきます。(文句を言わずCやれ、という話もありますが……)
次はこの本に真面目に取り組んで行きたいです。
- 作者: 渋川よしき
- 出版社/メーカー: Lambda Note
- 発売日: 2017/10/19
- メディア: テキスト
- この商品を含むブログを見る
また、このカンファレンスを通してもう一つ。会社で社内勉強会を運営したりしているのですが、気になっている問題がありました。それはテーマ設定の部分で、オンプレで動いてるシステムがあるような会社だと、なかなか全員を巻き込むような大きなものって開催がしづらいと感じています。ちゃんとテーマを絞った方が面白いものになるというのも理解はしているのですが、横断的な会も一回やってみたいなぁなんて思ってみたりしています。Goを社内でどれだけ導入しているところがあるかは分かりませんが……ちょっと調べてみます。
長々としたポエムになってしまいましたが、締めくくります。3倍近い倍率にもなったカンファレンスに参加できたのはめちゃくちゃ幸運でした。ブログだけではなく、コードのアウトプットもしなくては……!
おまけ
keynoteで発表してくれたdaveから、突然AirdropでみんなにGopherの画像が飛んでくるサプライズが。
Share your best gopher images!
— Dave Cheney (@davecheney) 2018年4月15日
とのことでした。素敵。
Android App Linksを試してみた
会社で資料を眺めていたら、今更ながらAndroid App Linksのことを知ったのでまとめてみます。
みなさんも、バックエンドチームが作ったシステム構成図に知らないAndroidの機能が書いてあって震えることありますよね
Android App Links
「外部のリンクを踏んだら特定のアプリを立ち上げたい」、なんてのはよくあることだと思います。入門書などでもActivity連携などで取り上げられてるテーマですし、そこで大体Intent(特に暗黙的Intent)の話につながっていく流れですよね。それを別の方向から実現するのが今回のAndroid App Linksです。
Android6.0で登場した機能なので、全世界で見るとちょっとまだ導入には厳しそうですが……。
何が出来るの
- deeplinkよりも起動するアプリを指定できます
- 立ち上げるアプリを事前に認証できます
- これ全部をAndroidStudioのウィザードで片付けることができます
詳しく見ていきましょう
deeplinkよりも細かく起動するアプリを指定できます
外部アプリを起動するとき、custom url schemeを使ってintent filterを設置するのがよくやる方法です。例えば、地図アプリを起動するときはgeo:
など、Androidデフォルトのアプリが提供しているものもあります。
Androidのユーザー側が自分で使用するアプリを決められるのがメリットですが、正直そんなに……という気持ちになります。それよりも、複数アプリが同じschemeを待ち受けている際に出てくるchooserのストレスの方が大きいような。
ここで「ALWAYS」を選んだばっかりに「アプリが立ち上がらない!」と問合せを受けるのは誰もが通る道だと思います。
そこで他と被らない独自のcustom url schemeを作り始めるのですが、httpスキーマじゃないとリンクとして認識してくれないメールやSMSクライアントがあったりしてちょっとつらいのが現状です。仕方ないのでこれまではWebサーバーにリダイレクトを設置して頑張っていたのですが、どう見てもコレジャナイ感があります。
一方、Android App Linksではhttpスキーマを使いつつ、パスまでしっかり使って起動するアプリを細かく指定できます。これで、chooserに悩まされるユーザーを減らすことができます。
立ち上げるアプリを事前に認証できます
Android App Linksとして指定するURLは、個人で所有するドメインを使う必要があります。正確に言うと個人で契約していなくてもいいのですが、事前にドメインに紐付いているサーバーにアプリ情報を含むjsonを上げておき、認証するというプロセスが必須です。これによって、得体の知れないアプリがlinkを侵食してくる問題を予防ことができます。
例えば、悪意あるアプリが決済系アプリと同じURLで待ち受けていた場合、ユーザーの選択によってはきれいに情報を引っこ抜かれてしまう恐れがあります。これを事前に防げるわけですね。
これ全部をAndroidStudioのウィザードで片付けることができます
(実は一部出来ていないようなところがあるのですが…)App Linksを設定するには、AndroidStudioのウィザードに沿って作業を進めるだけでOKです。公式ページや動画にもやり方は載っているのですが、以下で簡単にまとめておきます。
どうやるの
動画を参考に進めていきます。
まずは適当にprojectを生成しましょう。注意点は、minSdkVersionを23以上にすることくらいです。今回はkotlinを選択しました。
次に、AndroidStudioのTools->App Links Assistant
を選択します。
すると、こんな感じのメニューが出てきます。
この4ステップでやっていきましょう。
①Add URL intent filters
Host, Path, 起動したいActivityを設定します。
Hostにはリンクに使用したいwebサイトのURLのhost部分のみを入力してください。今回はgithub.ioを使用します。理由は後述します。
また、PATHのプルダウンメニューでは、今回とりあえずpathPrefix
を選んでおきましょう。pathの末尾の値を取得するために必要です。
Check URL Mappingに試しにURLを入力してみましょう。うまく設定できていれば、先程指定したActivityが出てくるはずです。
また、この時点で自動的にAndroidManifest.xml
にintent filterが設定されています。Previewを参考にしてください。
②Add logic to handle the intent
次にIntentを受け取って処理するロジックを、選択したActivityに自動で記述してくれる……はずなのですが、
駄目でした。。。Javaだと通ったのですが、Kotlinはまだ未対応のようです。
仕方ないので手で書いていきます。
MainActivity.kt
package tech.miliya.applinkssample import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) handleIntent() } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) setIntent(intent) handleIntent() } private fun handleIntent() { val appLinkAction = intent.action val appLinkData = intent.data if (Intent.ACTION_VIEW == appLinkAction && appLinkData != null) { val comment = appLinkData.lastPathSegment tv_comment.text = comment } } }
ついでに、xmlも少し弄っておきます。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/tv_comment" <!-- この行を追加 --> android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout>
一旦ここで、アプリを起動できるかテストしておきましょう。④Test on device or emulatorを選択します。
先程入力した、{HOST}/{PATH}/適当なコメント
を入力し、テストを実行します。
無事、準備したアプリが起動候補に出てきました。
いよいよここからアプリをドメインと紐付けていきます。
③Associate website
Site Domain(リンクとしたいURLのホスト)とApplication ID(アプリのパッケージ)を入力し、Generate Digital Asset Links File
を選択します。
すると、Previewにjsonファイルが表示されます。これを指定されたURLに配置していきましょう。
ここだけはHTTPSが必須です。準備するのが面倒なので、ここは動画の通りにgithub.io
を使ってホストしていきます。
Githubに{USERNAME}.github.io
の名前でリポジトリを作成し、指定された階層の通りに/.well-known/assetlinks.json
を作成します。
作業が完了したら、下の方にあるLink and Verify
をクリックしましょう。
note
github.ioでは、そのままだと.well-known
以下が見えないので、下記のファイルをリポジトリのルートに配置します。
.nojekyll
(空ファイル)_config.yml
include: [".well-known"]
④Test on device or emulator
最終動作確認です。
無事設定できていれば、先程のようにchooserが出てくることなく、いきなりアプリが起動します。
テキストが反映されていることも確認しておきましょう。お疲れ様でした。
まとめ
AndroidStudioにあるアシスタントを使いながら、Android App Linksを使ってアプリの外部起動のフローを実装してみました。
起動用URLの使い勝手の良さ、セキュリティ面ももちろんですが、chooserが出ないことをひたすら祈らなくてよくなるのは有り難いです。
しかもアシスタントが優秀で素晴らしい……!
Android6.0がもうちょっと広まれば、もっと積極的に使っていきたいです。
参考
developer.android.com developer.android.com www.youtube.com stackoverflow.com github.com