default eye-catch image.

ミドルウエア

この記事は自分の勉強のために書いています。 ソースはLaravel5.7の公式ドキュメントです。 新しいことは何もないので通常はそちらを参照してください。 [clink implicit=\"false\" url=\"https://readouble.com/laravel/5.7/ja/middleware.html\" imgurl=\"http://laravel.jp/assets/img/header.jpg\" title=\"Laravel 5.7 ミドルウェア\" excerpt=\"ミドルウェア\"] [arst_toc tag=\"h4\"] アプリケーションに送られてきたリクエストを途中でフィルタするのがミドルウェア。 認証とかCORSとかCSRFとか、デフォルトでミドルウェアが用意されているけれども、 ミドルウェアを自作することができる。 ミドルウェアの定義 新しいミドルウェアの作成 ミドルウェアを作成するartisanコマンドは以下の通り。 こうすると、app/Http/Middlewareの中にCheckCostクラスが作られる。 $ php artisan make:middleware CheckCost; CheckCostクラスの中でhandle()メソッドを実装する。 $requestを$nextに流す際に$closureを評価するように動作し、 結果として$requestと$nextの間のフィルタ処理を$closureに書くことができる。 <?php namespace AppHttpMiddleware; use Closure; class CheckCost { /** * 送信されたきたリクエストをフィルタする * * @param IlluminateHttpRequest $request * @param Closure $next * @return mixed */ public function handle($request, $closure $next) { if ($request->value < 100) { return redirect('hoge'); } return $next($request); } } ミドルウェアを複数使用するとき、 前の段のミドルウェアを通過した後、次の段のミドルウェアを通過する。 Before Middleware/ After Middleware 公式だと仰々しい名前が付いているのだけれども、 $nextを呼んだ後に処理をするか、呼ぶ前に処理をするか、の違いを表現できる。 つまり、リクエストを完了する前の処理なのか、後の処理なのか。 <?php namespace AppHttpMiddleware; use Closure; class CheckCost { /** * Before Middleware * リクエストを評価する前に処理する * * @param IlluminateHttpRequest $request * @param Closure $next * @return mixed */ public function handle($request, $closure $next) { //アクションを実行 return $next($request); } } namespace AppHttpMiddleware; use Closure; class CheckCost { /** * After Middleware * リクエストを評価した後に処理する * * @param IlluminateHttpRequest $request * @param Closure $next * @return mixed */ public function handle($request, $closure $next) { $response = $next($request); return $response; } } 登録 グローバルミドルウェア 要は全てのHTTPリクエストについてミドルウェアを通すやり方。 App/Http/Kernel.phpに書く。Kernelクラスにある$middlewareという配列に グローバルミドルウェアにしたいやつを並べていく。 <?php namespace AppHttp; use IlluminateFoundationHttpKernel as HttpKernel; class Kernel extends HttpKernel { protected $middleware = [ ... ] } 特定のルートのみにミドルウェアを設定 特定のルートに限定してミドルウェアを設定する場合もApp/Http/Kernel.phpに書く。 まず、$routeMiddlewareにミドルウェアの短縮キーを書く <?php $routeMiddleware = [ \'auth\' => AppHttpMiddlewareAuthenticate::class, \'auth.basic\' => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class, ... ]; そして、routes.phpでそれぞれのルートに定義した短縮キーを指定する。 短縮キーではなく完全なクラス名を書いても良いみたい。 ミドルウェアを複数書くと、その順番に通すことになる。 Route::get(\'admin/profile\', function() { })->middleware(\'auth\'); Route::get2(\'admin/profile2\', function() { })->middleware(\'CheckCost::class\'); Route::get3(\'admin/profile3\', function() { })->middleware(\'auth\',\'guest\'); ミドルウェアグループ 複数のミドルウェアをグループ化して、そのグループのミドルウェアを一気に当てることができる。 App/Http/Kernel.phpに書く。Kernelクラスにある$middlewareGroupという配列に グループ名をキー、ミドルウェアの配列を値として書いていく。以下のように。 <?php $middlewareGroup = [ \'web\' => [ \'auth\' => AppHttpMiddlewareAuthenticate::class, \'auth.basic\' => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class, ... ], ... ]; routes.phpの中では、それぞれのルートに定義したグループ名を与える。 例えば、以下だと\'/\'に対して\'web\'ミドルウェアグループを与えている。 つまり\'/\'に\'web\'ミドルウェアグループに定義したミドルウェアが順番にあたる。 Route::get(\'/\',function(){ })->middleware(\'web\'); routes/web.php に書いたルートには自動的に\'web\'ミドルウェアグループがあたる。 ミドルウェアパラメタ ミドルウェアパラメタの書き方 ミドルウェア実行時に引数を与えることができる。 書き方は以下の通り。handle()をオーバーライドする際に、引数として受けるパラメタを追加する。 例えば、パラメタ$paramを受け取るミドルウェアは以下の通り。 クエリパラメタにある$valが$paramである場合に限り処理を書いている。 <?php namespace AppHttpMiddleware; use Closure; class CheckCost { /** * 送信されたきたリクエストをフィルタする * * @param IlluminateHttpRequest $request * @param Closure $next * @return mixed */ public function handle($request, $closure $next, $param) { if ($request->val == $param) { return redirect(\'hoge\'); } return $next($request); } } ミドルウェア指定時に以下のようにパラメタを渡す。 T.B.D. 終了処理ミドルウェア T.B.D. サンプル 実行時間を計測するミドルウェアの試作 こちらを参考に試しに作ってみた。 https://qiita.com/niisan-tokyo/items/663300f8df1c6c89f0ae $ php artisan make:middleware TimerMiddleware Middleware created successfully. TimerMiddleware本体はこちら。 <?php namespace AppHttpMiddleware; use Closure; class TimerMiddleware { /** * Handle an incoming request. * * @param IlluminateHttpRequest $request * @param Closure $next * @return mixed */ public function handle($request, Closure $next) { $before = microtime(true); $res = $next($request); Log::debug(mictotime(true) - $before); return $res; } } ミドルウェアの登録は、app/Http/Kernel.phpの$routeMiddlewareに追加。 protected $routeMiddleware = [ .. AppHttpMiddlewareTimerMiddleware::class, ]; ルートの定義はこちら。 Route::get(\'timer\',function(){ })->middleware(\'timer\'); /timer にアクセスすると、storages/logs/laravel.logにログが残る。 ... [2019-01-29 16:05:07] local.DEBUG: 0.019489049911499

default eye-catch image.

ルーティング

この記事は自分の勉強のために書いています。 ソースはLaravel5.7の公式ドキュメントです。 新しいことは何もないので通常はそちらを参照してください。 [clink implicit=\"false\" url=\"https://readouble.com/laravel/5.7/ja/routing.html\" imgurl=\"http://laravel.jp/assets/img/header.jpg\" title=\"Laravel 5.7 ルーティング\" excerpt=\"基本的なルーティング\"] [arst_toc tag=\"h4\"] 基本的なルーティング routes.phpの書き方 HTTP動詞に対応させたroutes.phpの書き方。RESTfulAPIを書く場合は必要なんだと思う。 anyを使うと全ての動詞に対応する。matchを使うと対応する動詞を選べる。 No 動詞 文法 1 GET Route::get($url,$callback); 2 POST Route::post($url,$callback); 3 PUT Route::put($url,$callback); 4 PATCH Route::patch($url,$callback); 5 DELETE Route::delete($url,$callback); 6 OPTIONS Route::options($url,$callback); 7 全て Route::any($url,$callback); 8 選んだ動詞 Route::match($array_of_verb,$url,$callback); リダイレクトルート 単純にリダイレクトをかける場合は、わざわざコントローラを通さなくて良い。 (コントローラを通してた...)。 通常302で、301もルーティングだけで対応可能。 No HTTP Status Code 文法 1 302 Route::redirect(\'here\',\'there\'); or Route::redirect(\'here\',\'there\',302); 2 302 Route::redirect(\'here\',\'there\',301); or Route::permanentRedirect(\'hear\',\'there\'); Viewルート ルートから直接ビューを返す場合はコントローラを介さなくてもよい。 No文法 1Route::view($url,$view_name); 2Route::view($url,$view_name,$array_of_parameters); ex, Route::view($url,$view,[\'param1\'=>100,\'param2\'=>200]); CSRF保護 webルートに定義したPOST,PUT,DELETEへのルートへ送信するフォームは 一緒にCSRFトークンを送る必要がある(CSRFは別にまとめる予定)。 ルートパラメータ URLの動作に必要なパラメタをクエリ文字列ではなくURLに含める書き方。 必須パラメータとしておけば省略は不可となり存在チェックを省略できる。 必須パラメータ Curly bracketsでパラメタ名を囲んで定義する。 受けるクロージャはそのパラメタ名をもつ引数を使用する。 Route::get(\'application/{application_id}\', function($application_id) { return $application_id; }); 任意パラメータ Curly brackets内のパラメタ名を\"?\"で終わらすと任意パラメータになる。 受けるクロージャでは必ずデフォルト引数を設定する。 Route::get(\'user/{name?}\',function($name = null) { return $name; }); 正規表現制約 ルートパラメタのフォーマットを正規表現で制約できる(そんなことできるんだ...)。 書き方は以下の通り。URLとクロージャは触らない。 Route::get(\'user/{name}\', function($name) { return $name; })->where(\'name\',\'[A-Za-z]+\'); グローバル制約 RouteServiceProviderのbootメソッドに全てのルートパラメタを制約するルールを書ける。 例えば以下のように書いておくと、全てのルートの\"id\"という名前のクエリパラメタが数値に制約される。 /** * */ public function boot() { Route::pattern(\'id\',\'[0-9]+\'); parent::boot(); } /** * routes.php */ Route::get(\'user/{id}\',function($id) { return $id; // $idは数値に制約 }); 名前付きルート 定義したルートに名前を付けておくことで、そのルートのURLを名前から引くことができる。 名前付きルート 例えばuser/{id}に対するルートにmypageという名前を付けるには以下。 Route::get(\'user/{id}\',function($id) { return $id; })->name(\'mypage\'); 名前付きルートへのURL取得 このルートへのURLはroute()により取得できる。 ルートが必須/任意パラメータを必要とする場合は第2引数で値を与える。 redirect()->route(\'mypage\',[\'id\'=>100]); ルートグループ 複数のルートで共通の属性(=ミドルウェアと名前空間)をまとめて書ける。 グループはネストすることができる。ミドルウェアと名前空間は別途記載。 ミドルウェアによるグループ化 複数のルートで共通のミドルウェアを外出しする書き方は以下。 (もちろん個別に書くこともできる) Route::middleware([\'first\',\'second\'])->group(function() { Route::get(\'/\',function(){ // firstミドルウェア、secondミドルウェアを順番に使用 }); Route::get(\'/hoge/{id}\',function($id){ // firstミドルウェア、secondミドルウェアを順番に使用 }); }); 名前空間によるグループ化 複数のルートで同じPHP名前空間を使用する書き方は以下。 こうすることで、各コントローラのファイルの先頭に名前空間を書かなくてもよくなる。 Route::namespace(\'Namespace1\')->group(function() { // AppHttpControllersNamespace1 名前空間下のコントローラ }); })/ <?php namespace App/Http/Controllers/Namespace1; // <- これが不要になる サブドメインによるグループ化 複数のルートが同じサブドメイン配下にあるような配置にする書き方は以下。(こんなことできるのか!)。 Route::domain(\'{subdomainname}.myapp.test\')->group(function() { Route::get(\'user/{id}\',function($id) { // subdomainname.myapp.test/user/100 }); }); ルートプレフィックスによるグループ化 複数のルートが同じ文字列からスタートするように配置する書き方は以下。 Route::prefix(\'sameprefix\')->group(function() { Route::get(\'user/{id}\',function($id) { // /sameprefix/user/100 }); Route::get(\'group/{id}\',function($id) { // /sameprefix/group/200 }); }); ルート名プレフィックスによるグループ化 複数のルートのルート名が同じ文字列からスタートするように配置する書き方は以下。 Route::name(\'sameprefix\')->group(function() { Route::get(\'user/{id}\',function($id) { // \'sameprefix.user\' というルート名 })->name(\'user\'); Route::get(\'group/{id}\',function($id) { // \'sameprefix.group\' というルート名 }); }); モデル結合ルート ルートで与えた情報(多くの場合でモデルID)をコントローラに渡して コントローラの中でモデルインスタンスを引くという使い方がほとんどなので、 ルートに含めたモデルIDからモデルインスタンスを解決してコントローラでそれを使うことができる。 当然、クロージャを直接書いても同じ。 暗黙のモデル結合ルート どうやってルートパラメータとモデルインスタンスを紐づけるかというと、 なんと、PHPのタイプヒントを使って紐づける。 以下、$userがAppUserクラスであるとタイプヒントされている。 ルートパラメータにuserがあるから、クロージャの中ではAppUserのインスタンスを $userを使って引いたオブジェクトを使用できる。 わざわざ$userを使ってAppUser::find($user)と書いたりエラーケースを書くのを省略できる。 AppUser::find($user)が見つからなかったらルートは404を返す。すごいー。 Route::get(\'users/{user}\',function(AppUser $user) { // AppUser $user を使用できる。 }); キー名がカスタマイズされたモデル結合ルート モデルの主キーがidではないときimplicitにidからModelを引くことが出来ない。 モデル側でgetRouteKeyName()をオーバーライドして主キーを返せば良い。 ルートパラメータを新しい主キーであるものとしてインスタンスを探してくれる。 Route::get(\'users/{user}\',function(AppUser $user) { // AppUser.userid が $user であるインスタンスを使用できる。 }); /** * モデルの中 * */ public function getRouteKeyName() { return \'userid\'; } 明示的なモデル結合ルート ルート名とタイプヒントを使ってimplicitにモデル結合する方法とは別に、 RouteServiceProviderクラスのboot()メソッドの中でexplicitにモデル結合を定義できる。 タイプヒントがなくても良いというのならexplicitに書く意味もあると思うのだけど、 タイプヒントが必要なのであれば、implicitな記法に対して冗長な気がするんだけども...。 /** * RouteServiceProvider * */ public function boot() { parent::boot(); Route::model(\'user\',AppUser::class); } /** * routes.php * */ Route::get(\'user/{user}\',function(AppUser $user) { }); 依存解決ロジックのカスタマイズ 「ルートパラメータをモデルID、タイプヒントをモデルクラスとして、モデルIDを主キー(または別のキー) を使って検索する」という単純なロジックとは異なるロジックを定義する方法が用意されている。 例えばモデルを主キーとは関係ないフィールドで検索したいときは以下みたいに書く。 /** * RouteServiceProvider * */ public function boot() { parent::boot(); Route::bind(\'user\', function($value) { return AppUser::where(\'name\',$value)->first() ?? abort(404); }); } モデル側でresoluveRoutingBind()をオーバーライドすることでも実現可能。 /** * モデル * */ public function resolveRoutingBind($value) { return $this->where(\'name\',$value)->first() ?? abort(404); } その他 フォールバックルート フォールバックルートを書いておくと、routes.phpに書いたどのルートにも当たらなかったときに使われる。 通常404になるところを受ける。書く場合は最後に配置しないといけない。 Route::fallback(function(){ // do something when 404. });

default eye-catch image.

とりあえずチャットアプリを作ってみる

Kotlin製のChat UI Libraryを写経しながらAndroidアプリを作る練習。 もちろんサーバ側は無いので、文字列決め打ちで出してるだけ。 昔作ったJava製のものと基本的なことは変わっていない様子。 https://github.com/bassaer/ChatMessageView.git null安全性の絶対的な安心感。これに尽きる。。 自分のような素人が書いてもぬるぽで落ちないというのは奇跡!。 Swiftとそっくりという噂なので、 もしかするとKotlinからSwiftに移植するのは簡単なのかも。

default eye-catch image.

一覧から詳細の起動_一覧側(MainActivity)

一覧から詳細を起動する一覧側(MainActivity.kt)の実装。 ListViewのsetOnItemClickListener()の書き方。 setOnItemClickListener()の引数はAdapterView.OnItemClickListenerオブジェクトを取る。 OnItemClickListenerオブジェクトは1つの仮想関数(Single Abstract Method)を持っていて、 それを実装したものを渡すことになる。 本来、OnItemClicListenerを派生したオブジェクトでSAMを実装したものを渡す、という とても長い記述になるのだけれども、仮想関数が1個だけならば代わりにラムダ式を1個渡せば良い。 (SAM変換)。 ActivityからIntentを取得してIntentからActivityを起動する、という書き方。 Intentを取得するときにパラメタ(今回の場合はArticleインスタンス)が渡る。 package com.example.ikuty.myapplication import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.widget.ListView import com.example.ikuty.myapplication.model.Article class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val listAdapter = ArticleListAdapter(applicationContext) val dummyArticle1 = dummyArticle(\"ikuty.com記事1\",\"ikuty1\") val dummyArticle2 = dummyArticle(\"ikuty.com記事2\",\"ikuty2\") listAdapter.articles = listOf(dummyArticle1,dummyArticle2) val listView: ListView = findViewById(R.id.list_view) as ListView listView.adapter = listAdapter listView.setOnItemClickListener { adapterView,view, position, id -> val article = listAdapter.articles[position] ArticleActivity.intent(this, article).let { startActivity(it) } } } }

default eye-catch image.

一覧から詳細の起動 – ParcelableなインスタンスをIntentに付与してActivityを起動する

記事一覧画面でアイテムを触るとそのアイテムの詳細画面が開くというのをやりたい。 記事詳細画面のController(Activity)を書く。 Intent まずIntent。Activityを起動する際にパラメタの役割を果たす。 Intentには「意図」「目的」の意味がある。Activityを起動する意図とか目的とか。 ベースとなるIntentに付加的な情報を付与(putExtra)することで「意図」「目的」となる。 Intentには基本型の他にカスタムオブジェクトを付与することができる。 ただし、カスタムオブジェクトはParcelableインターフェースを実装している必要がある。 あるActivityから別のActivityを起動する際のテクニックとして、 起動される側のActivityが持つintent()メソッドを使ってIntentを作成し(つまりパラメタを渡し)、 作成したIntent経由でActivityを起動する。 companionオブジェクトにfun intent(context: Context, article: Article): Intentを作る。 このintent()を経由すれば起動に必要なパラメタ(今回はarticle: Article)を付与したIntentを取得できる。 intent()内ではputExtra()を使ってarticleとIntentを紐づけている。 onCreate(savedInstanceState: Bundle?) onCreate()が発火したときにはIntentにParcalableなArticleインスタンスが付与済み。 Activityが持つintentからgetParcelableExtra()によりArticleインスタンスを取得して、 ビューにそのArticleインスタンスを表示する。 package com.example.ikuty.myapplication import android.content.Context import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.webkit.WebView import com.example.ikuty.myapplication.model.Article import com.example.ikuty.myapplication.view.ArticleView class ArticleActivity: AppCompatActivity() { companion object { private const val ARTICLE_EXTRA: String = \"article\" fun intent(context: Context,article: Article): Intent = Intent(context, ArticleActivity::class.java) .putExtra(ARTICLE_EXTRA, article) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_article) val articleView = findViewById(R.id.article_view) as ArticleView val webView = findViewById(R.id.web_view) as WebView val article: Article = intent.getParcelableExtra(ARTICLE_EXTRA) articleView.setArticle(article) webView.loadUrl(article.url) } }

default eye-catch image.

リストビューを表示するためにListViewAdapterを実装する

ListViewAdapter リストビューを表示するためにListViewAdapterを実装する。 BaseAdapterを継承して必要なメソッドを実装する。 実装が必要なのは以下。 fun getCount(): Int fun getItem(position: Int): Any? fun getItemId(position: Int): Long fun getView(position:Int,convertView: View?,parent: ViewGroup?): View fun getCount(): Int 名前の通り、リストビューが保持するアイテムの個数を返す。 ListViewAdapterが保持するList<Article&gtのサイズを返すようにする。 fun getItem(position: Int): Any? 名前の通り、指定した位置のアイテムを返す。 保持するList<Article&gtの要素を返すようにする。 fun getItemId(position: Int): Long 指定した位置のアイテムに関するアプリケーション固有のIDを返す。 ListViewの外から「位置->固有ID」を取得できる。 アイテムを取得してアイテムからIDを取るんじゃダメなのか...。 Get the row id associated with the specified position in the list. fun getView(position:Int,convertView: View?,parent: ViewGroup?): View 指定した位置のアイテムを表示するためのビューを取得する。 convertViewには、画面から表示しきれなくなったViewが来る。 既に画面に表示されているならばnullが来る。 画面から表示しきれなくなったView(convertView)を使い回すことで負荷削減する。 Get a View that displays the data at the specified position in the data set. convertViewをArticleViewにダウンキャストした結果をViewとする。 ダウンキャストがnullならばエルビス演算子(?:)の右辺が評価され、新しいArticleViewが作られる。 View = ((convertView as? ArticleView) ?: ArticleView(context)).apply { setArticle(articles[position]) package com.example.ikuty.myapplication import android.content.Context import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter import com.example.ikuty.myapplication.model.Article import com.example.ikuty.myapplication.view.ArticleView class ArticleListAdapter(private val context: Context): BaseAdapter() { var articles: List = emptyList() override fun getCount(): Int = articles.size override fun getItem(position: Int): Any? = articles[position] override fun getItemId(position: Int): Long = 0 override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View = ((convertView as? ArticleView) ?: ArticleView(context)).apply { setArticle(articles[position]) } }

default eye-catch image.

ListViewの1行を表示するViewControllerを書いてみる

ボキャブラリーが貧弱でアレなんだけども、やりたいことはそんな感じ。 リスとビューの1行に表示するアイテムのViewCOntrollerを書いてみる。 1行に前回定義したArticleを表示する予定なので、そういう文脈になっている。 findViewById()ではないKotlin風のViewの取り方 findViewById()を使うことで、IDから該当するViewを取ることができるけれど、 その応答値はnullableなので、findViewById()を使って取ったViewを使いまわそうとすると、 nullableに対するケアがつきまとうようになる。 Kotlinのlazy()を使うことでnullableでないViewの取り方を実現できる! lazy()は移譲プロパティの移譲先を書く書き方。公式の仕様はこちら。 遅延プロパティ、つまりアクセスされたときに初めて評価されるプロパティを実現する。 以下、Viewに<T: View&gt bindView(@IdRes id:Int)という拡張関数を追加している。 追加した拡張関数の戻りがnullを許容しないから、 これを経由すればnullのケアをしなくて良くなる(という話) lazy()の中で評価したfindViewById()がnullを返した場合、ダウンキャストの失敗を どう扱うんだろうか。 package com.example.ikuty.myapplication import android.support.annotation.IdRes import android.view.View fun View.bindView(@IdRes id: Int): Lazy = lazy { findViewById(id) as T } 実装 この拡張関数を使ったArticleViewは以下。 プロパティとして保持するViewがnull非許容になっている。 それにより、setArticle(artilce:Article)において、 プロパティにアクセスする際にnullのケアをしないで済んでいる。 これは便利なんじゃなかろうか。 package com.example.ikuty.myapplication.view import android.content.Context import android.graphics.Color import android.util.AttributeSet import android.view.LayoutInflater import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView import com.example.ikuty.myapplication.R import com.example.ikuty.myapplication.model.Article import com.example.ikuty.myapplication.bindView class ArticleView : FrameLayout { constructor(context: Context?) : super(context) constructor(context: Context?, attrs: AttributeSet?) : super(context,attrs) constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context,attrs,defStyleAttr) constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context,attrs,defStyleAttr,defStyleRes) val profileImageView: ImageView by bindView(R.id.profile_image_view) val titleTextView: TextView by bindView(R.id.title_text_view) val userNameTextView: TextView by bindView(R.id.user_name_text_view) init { LayoutInflater.from(context).inflate(R.layout.view_article,this) } fun setArticle(article: Article) { titleTextView.text = article.title userNameTextView.text = article.user.name profileImageView.setBackgroundColor(Color.RED) } }

default eye-catch image.

Percelableを実装してIntentに乗せてBundleに渡せるモデルを書く

Percelable interface モデルをActivity間でやりとりしたり、再生成時の復元処理を書いたり、など、 Percelableというインターフェースを実装すれば、面倒みてくる。 Bundleには基本的な型以外に、Percelableを実装したインスタンスを渡すことができる。 (つまり、ActivityとFragmentに渡すことができる。) Percelableを実装するとIntentに載せることができる。 Parcelable interfaceは2つの仮想関数を持っていてそれぞれ実装する。 CREATORというstaticメンバを実装する。 abstract describeContents() abstract writeToPercel(Percel dest, int flags) CREATOR abstract describeContents() describeContents()はひとまず0を返すようにする。 0以外の応答値が必要な場合についてひとまず省略。 abstract writeToPercel(Percel dest, int flags) writeToPercel(Percel dest, int flags)はPercelに保存するデータを列挙する。 通常クラスが持つプロパティを列挙する。 CREATOR CREATORという名前のstaticフィールドを実装する必要がある。 Interface for classes whose instances can be written to and restored from a Parcel. Classes implementing the Parcelable interface must also have a non-null static field called CREATOR of a type that implements the Parcelable.Creator interface. Kotlinはstaticフィールドに対応していない(companionオブジェクトが対応する)ため、 companionオブジェクトとして実装する必要がある。 Percelable.Create<T>というobject式(シングルトンオブジェクト)を継承して作る。 Kotlinから呼ぶだけなら、companionオブジェクトを用意するだけで外からstaticメンバのように扱えるけれども、 Javaから呼ぶなら、さらに@JvmFieldを付ける必要がある。 Kotlnでプロパティを書くと、Javaに変換する際にgetXXX()やsetXXX()のように 自動的にgetter/setterが付く(らしい)。 プロパティに@JvmFieldアノテーションをつけると、getXXX()/setXXX()ではなく、 直接フィールドを触るように変換される。 @JvmFieldを付けずにstaticフィールドを書こうとすると、 勝手にgetXXX()/setXXX()が付いてしまい要求を満たせなくなる。 (JVM言語っぽいところを初めて理解した図...) 実装例は以下参照。 スコープ関数,run ここ見ながら理解。 任意の型Tの拡張関数で、そのTをレシーバとする関数Rを引数にとる。 public inline fun T.run(f: T.() -> R): R = f() 例えば、文字列\"AIUEO\"を小文字にする例。 val str = \"AIUEO\".run { toLowerCase() } println(s) // aiueo 実装例 Kotlinの文法の基本を詰めていれば問題なし。 package com.example.ikuty.myapplication.model import android.os.Parcel import android.os.Parcelable data class Article(val id: String, val title: String, val url: String, val user: User) : Parcelable { companion object { @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { override fun createFromParcel(source: Parcel): Article = source.run { Article(readString(),readString(),readString(), readParcelable(Article::class.java.classLoader)) } override fun newArray(size: Int): Array = arrayOfNulls(size) } } override fun describeContents(): Int = 0 override fun writeToParcel(dest: Parcel, flags: Int) { dest.run { writeString(id) writeString(title) writeString(url) writeParcelable(user, flags) } } }

default eye-catch image.

Kotlinスタートブックを読み始める

断片的な情報を集めるよりも、評判が良い良書を読む方が確実だし結果的に理解が早いと思う。 なので、超入門に続いて、Kotlin中級書として最も評判が良いだろう本を読み始めてみる。 何事もアウトプットしないと理解が定着しないので、引き続きWPへ転記。 超入門に使った書籍が薄すぎて...と思っていたのだけど、 もしかすると、さっそくKotlinの簡潔さに触れたのかもしれない。 言語仕様からくだらない拘りを捨てて、いかに現実的な書き方を実現するか、というところ。 これは良いかもしれないぞ、と。 Kotlinスタートブックposted with amazlet at 18.12.10リックテレコム (2017-03-21)売り上げランキング: 68,877Amazon.co.jpで詳細を見る 実際読んでみると、超入門で使ったKindle本で良かったかなと思う。 確かに上の方が書いてある幅が広いんだけど、下の方が要点がまとまっている気がする。 こちらの方が薄いし500円で買えるし。 速習 Kotlin: Javaより簡単!新Android開発言語を今すぐマスター 速習シリーズposted with amazlet at 18.12.10WINGSプロジェクト (2018-09-26)売り上げランキング: 1,981Amazon.co.jpで詳細を見る Amazon Cyber MondayでKindle Paper whiteを手に入れたので、 この手の本は、今後はKindleで入手する見込み。

default eye-catch image.

Swift超入門_関数とクロージャ的な何か

仮引数と省略 swift初心者(おっさん)が一見して理解できなかったのが関数の仮引数の前にあるアンダースコア(_)。 swift全般的にアンダースコアが言語仕様に含まれているので、いろいろ調べてみた。 単に、関数を呼び出す際にキーワード引数の名称を省略するための記法だった。 仮引数の前にアンダースコア(_)を置くと、呼び出し時にキーワード無しで引数を呼べる。 func hoge(_ int: Int) { print(int) } hoge(100) func hoge2(int: Int) { print(int) } hoge2(int: 200) Swiftは、仮引数と実引数を違う名前で定義できる。 以下、param2というキーワードを使ってhoge3を呼び出しているが、 hoge3の中では仮引数param2をparam3として使っている。 一般に、param3をparam1と関係がある名称にすることで冗長にさせない工夫。 func hoge3(param1: Int, param2 param3: Int) { print(\"(param1),(param3)\") } hoge3(param1: 100, param2: 200) デフォルト引数は一般的。 func hoge4(param1: Int, param2 param3: Int = 200) { print(\"(param1),(param3)\") } hoge4(param1: 100) 関数の定義名に特徴がある。hoge4の定義名は以下のようになる。 ObjectiveCっぽい。 hoge4(param1:param2) インアウト引数 いわゆる参照渡し。 仮引数にinout予約語をつけておくと、仮引数の操作が実引数に反映される。 呼び出す際に、引数に&をつける。 func hoge5(param1: Int, param2: inout Int) { param2 = 300 print(param1, param2) } var val1 = 100 var val2 = 200 hoge5(param1: val1, param2: &val2) // 100 200 print(val2) // 300 可変長引数 可変長引数はスリードット(...)を指定し、配列(Array)で受ける。 func hoge6(params: String...) { for param in params { print (\"element is (param)\") } } hoge6(params: \"hoge1\",\"hoge2\",\"hoge3\") クロージャ さて、Swiftのクロージャ。まぁ何回か使ったら慣れるだろうぐらい分かりづらくはない。 前置詞のinがクロージャブロックの開始になってるのは意図があるんだろうか。 let double = { (x:Int) -> Int in return x * 2 } let v = double(2) クロージャの引数の型と戻り値の型は型推論が効く。 var closure: (Int) -> Int closure = { (x:Int)-> Int in return x*x } closuer(2) // 4 var closure2 = { x in return x*2 } closure2(3) // 6 クロージャの簡易引数名 クロージャの引数名すら書きなくない場合もある。 前置詞inではなく、一般的なブラケットでクロージャブロックを書く。 クロージャブロックからは、n番目のパラメタを$nとして受け取る。 let add: (Int,Int)->Int = { return $0+$1 } add(2,3) // 5 クロージャによる変数束縛 クロージャの実行時にクロージャ呼び出し元の環境を利用できる。 文脈によって呼び出し元の環境は異なるので、実行時に環境のスナップショットを取るイメージ。 Swiftでは\"キャプチャ\"という名前で呼んでいるらしい。 例えば以下。クロージャ add:(Int,Int)-> Int 内で呼び出し元の変数pを使っている。 クロージャ呼び出し時に呼び出し元の環境に依存してpの値が決まる。 なお、pが初期化済みでない状態でクロージャを定義するとエラーになる。 クロージャの実行前にキャプチャする変数は初期化されている必要があるらしい。 var p : Int = 200 let add: (Int,Int)->Int = { return $0+$1 + p } p = 200 add(2,3) // 205 p = 300 add(2,3) // 305 クロージャ内でキャプチャした変数を書き換えた場合、キャプチャ元になった変数自体が変わる。 autoclosure属性 引数の遅延評価を行う指令。 論理和を計算する関数を定義しようとしたとき、Bool型の引数を2つ取ってBool型の戻り値を返す 関数を定義するとする。 論理和なので、どちらかが真であれば一方の引数を見なくても結果がわかるのだが、 下記のようにすると、どちらかが真であっても、もう一方の引数を評価して返すことになる。 func or(_ lhs:Bool, _ rhs:Bool) -> Bool { if lhs { return true } else { return rhs } } or(true,false) 引数をクロージャとし、クロージャ内で引数の評価の必要性を判断するようにして、 クロージャを遅延評価(関数内での評価)することで不要な引数の評価を回避するというアイデア。 func or2(_ lhs:Bool, _ rhs:()->Bool) -> Bool { if lhs { return true } else { let rhs = rhs() return rhs } } func lhs() -> Bool { return true } func rhs() -> Bool { return false } or2(lhs(),{return rhs()}) or2の第2引数が複雑になってしまうところを、文法解決で簡単に書けるようにするのが@autoclosure。 引数をクロージャで包む書き方を省略できる。 下記のように書ける。 func or3(_ lhs:Bool, _ rhs: @autoclosure ()->Bool) -> Bool { if lhs { return true } else { let rhs = rhs() return rhs } } func lhs() -> Bool { return true } func rhs() -> Bool { return false } or3(lhs(),rhs()) trailing closure 引数の最後がクロージャである場合、その引数の前で引数のブラケットを閉じて、 その後にクロージャを書くことができる。 例えば以下のような感じ。 1つ目は真面目に引数の最後にクロージャを与えている。 2つ目は関数呼び出しの後にクロージャを書いている。 func execute(parameter:Int, closure: (String)->Void) { closure(\"parameter is (parameter)\") } execute(parameter:1, closure:{ string in print(string) }) execute(parameter:2) { string in print(string) } 関数をクロージャとして扱う 関数をクロージャとして扱うことができる。関数double(_:)は(Int)->Int型。 それをfunctionという定数の初期値に設定している。 functionには型アノテーションがないけれども、 double(_:)が(Int)->Intなので型推論されて(Int)->Intになる。 func double(_ x:Int)->Int { return x*2 } let function = double function(2) 関数の引数としてクロージャを与えるシーンで、 クロージャを関数として用意しておくことで、それぞれの呼び出しでクロージャを定義する必要がなくなる。 下記について、上はmapの引数としてそれぞれクロージャを定義して呼び出している。 同じ処理をするのに2回定義しないといけない。 下は、関数を定義し、mapの引数として関数をクロージャとして与えている。 関数(クロージャ)の定義は1回で良い。 let array1 = [1,2,3] let toArray1b = array1.map{$0*2} let array2 = [4,5,6] let toArray2b = array2.map{$0*2} func double(_ x:Int)->Int { return x*2 } let toArray1a = array1.map(double) let toArray2a = array2.map(double)