くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

Retrofitの基本的な使い方

今回はRetrofitの基本的な使い方について書きたいと思います。

Android開発でAPI連携を行う場合に必ずと言っていいほど使うと思います。たまにハマってしまうので、まとめようかと思います。

Retrofitは、APIから取得するJSONデータを、オブジェクトにしてアクセスできるようにするまでを面倒見てくれるHTTP clientです。

通信はOkhttpというライブラリを合わせて使うことが多いと思います。今回はOkHttpの説明は省きたいと思います。

では使い方をみていきましょう!!

Retrofitの使い方

使い方はかなりシンプルです。

  1. gradleでの設定追加
  2. Interface実装
  3. HttpClient実装

gradleの設定追加に関しては下記自分が書いた記事をみてください!

RxjavaとRetrofitで非同期通信を行う

今回は2の「Interface実装」を少し詳しくみていきます。

リクエストメソッド

下記が1番シンプルなパターンです。

@GET("api")
val api(): Call<Response>

上記の場合だと、実行されるAPIは「http://○○○○○○○○○○○○○○○○/api」という形になります。ここでは説明を省いてましたが、URLのapi以前はHttpClient実装で定義することが多いと思います。

@GETをつけることでGETメソッドであることを明示します。

また、下記のように直接パラメータをつけることも可能です。

@GET("api/list?sort=desc")
val api(): Call<Response>

URLを動的に扱う

URLを動的に扱う場合は下記のようにします。

@GET("api/{id}/list")
fun getList(@Path("id") groupId : Int) : Call<Response>

{}で囲んで命名し、@PathをNameと一緒に定義することで渡したパラメータがURLパスの中に代入されます。 今回の場合はidを動的にしてURLでその値を受け取ってリクエストを作ります。

また、@Pathの他に@Queryというものもあります。

@GET("api")
fun getList(@Query("id") groupId : Int) : Call<Response>

こうするとAPIのURLは「http://○○○○○○○○○○○○○○○○/api?id=○○」になります。後ろにをつけてパラメータを追加する形になります。 GETリクエストで多い形式です。

リクエストボディ

先ほどのURLの末尾にをつけてパラメータを追加する物とは別に、インスタンスがもつ要素をJson形式に変換してパラメータに追加する形式があります。 その場合はインスタンスクラスを引数にするインターフェースを用意し、@Bodyをつけます。

これはPOSTやPUTなどでよく使われます。

場合によりますが、GETとPOSTで@Bodyを使うか@Queryを使うか変わってくるので確認しましょう!これで自分は苦労しました。(今回はこれが理由で記事書いてます)

@POST("users/create")
fun createUser(@Body user: User): Call<Response>;

RetorfitではHeaderを定義するることもできます。

@Headers({
    "Accept: application/json",
    "token: AAAAAAA"
})
@GET("api")
val api(): Call<Response>

単体指定、複数指定も可能です。ただ、動的な値を指定する場合は悪鬼のようにするかOkHttpのIntercepterで動的に作成することになると思います。

@GET("api")
val api(@Header("token") token: String): Call<Response>

// もしくはマップ形式で渡すことも可能
@GET("user")
val api(@HeaderMap Map<String, String> headers): Call<Response>

FORM ENCODED

application/x-www-form-urlencoded (key=value) 形式に自動で変換して欲しい場合は@FormUrlEncoded@Filedを使います。

@FormUrlEncoded
@POST("user/edit")
val updateUser(@Field("first_name") firstName :String, @Field("last_name") lastName :String): Call<Response>

@FormUrlEncodedUTF-8エンコードしてしまうので、他の文字コードで POST する必要がある場合は@Bodyを使う必要があります。

MULTIPART

Retrofitは画像やファイルなどマルチパートなデータ送信にも対応しています。 次のように@Multipartをつけ、送りたいデータには@Partあるいは@PartMapをつけます。

@Multipart
@PUT("user/photo")
fun updateUser(@Part("photo") photo: RequestBody): Call<User>

ただ、@Multipart@FormUrlEncodedはContent-Typeが矛盾するので併用できません。

HttpClient実装

最後にHttpClientの実装についても軽く触れます。 Retrofitのフレームワークを使い、APIを定義したInterfaceをインスタンス化します。

val retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

val service = retrofit.create(GitHubService.class)

このserviceがInterface(APIを定義したもの)であり、実際にAPIコールを行う際に利用します。

また、RetrofitではhttpClientの実装としてokhttpを使用するのが一般的ですが、自分でOkHttpのインスタンスを指定することが出来ます。

val okHttpClient = OkHttpClient.Builder()
        .addInterceptor(new HttpLoggingInterceptor().setLevel(Level.BODY))
        .build();

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.github.com/")
 .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create())
    .build();

val service = retrofit.create(GitHubService.class)

今回はログ出力の設定を入れているだけですが、ヘッダーの設定やその他いろんな設定を加えることができます。

Gsonについて

HttpClient実装でGsonConverterFactory.create()というファクトリーメソッドを設定しました。
今回Gsonを使用するので、ファクトリーメソッドを設定しないとJSONをオブジェクトに変換できないためです。

ここでGsonについて補足しようと思います。

Gsonは、Googleが提供するJSONデータとオブジェクトを相互に変換するためのライブラリです。 具体的には、JSONデータをレスポンスのクラスにマッピングした状態に変換するときに使用するクラスです。

オブジェクトをマッピングする場合には次のようにして使用します。

data class TestData(
    var firstName: String,
    var secondName: String
)

val strJson = "{firstName=A, secondName=B}"
val testData = Gson().fromJson(strJson, TestData::class.java)
val firstName = testData.firstName

リストをマッピングする場合には次のように使用します。

val strJson = "[{firstName=A, secondName=B},{firstName=AAA,secondName=BBB}]"
val listType = object : TypeToken<List<TestData>>() {}.type
val testDataList = Gson().fromJson<List<TestData>>(strJson, listType)
val firstName = testDataList[0].firstName

実際にAPIで使用する場合にはマッピングするクラスを指定すればいいだけなので、上記のような処理は基本的には行わないですが、 Jsonのレスポンスをマッピングする場合には上記のようにしてマッピングをするというのを覚えておくといいかもしれません。

参照

Retrofit公式ドキュメント

Android Retrofit2 with Kotlin

iOSのシミュレーターにプッシュ通知を送る

今回はプッシュ通知のテストについて書きます。

プッシュ通知のペイロードはわかっていてもプッシュ通知の機能がまだない場合ありませんか?

業務でたまたま発生してプッシュ通知のテストがシミュレーターでできることを知ったので方法をまとめたいと思います。

前提としてApple Push Notification Service payloadという以下のようなフォーマットのJSONファイルが必要になります(拡張子は .apnsのファイル)

{
    "aps":{
        "alert":"これはテストです",
        "sound":"default",
        "badge":1
    }
}

JSONファイルをシミュレータにドラッグアンドドロップ

この方法が1番簡単だと思います。

Simulator Target BundleにプロジェクトのBundle IDを入力します

他にペイロードのデータなどはプロジェクトによって追加してください

{
    "Simulator Target Bundle": "",
    "aps":{
        "alert":"これはテストです",
        "sound":"default",
        "badge":1
    }
}

上記のようなapnsファイルをシミュレータにドラッグアンドドロップすればプッシュ通知をシミュレーターに送ることができます

コマンドラインで送る

下記のようにsimctlを使う方法もあります。この場合Simulator Target Bundleは不要です

$ xcrun simctl push <シミュレーターのDeviceID> <BUNDLE_ID> <APNS_FILE_NAME>

SIMULATOR_DEVICE_ID は次のコマンドで調べることができます。

xcrun simctl list

作成したapnsファイルをもとにプッシュ通知がシミュレーターに送られるはずです

apnsファイルを作成する際に"aps"はないとうまくいかないと思うので、注意してください

UITableViewでカスタマイズヘッダーを使う

しばらく更新していないうちに年を越してしまいました。。。。。

これからはなるべく更新していく予定です

今年初の記事はUITableViewでカスタマイズヘッダーを使う方法です

UITableViewではstoryboardで作成することができるのですが、下記のようにヘッダーやフッダーをカスタマイズが難しいです。

f:id:kumaskun:20210208001930p:plain
storyboardでのヘッダー・フッター作成イメージ

では、カスタマイズする場合にはどうするかを説明したいと思います。

レイアウトをxibで作成

まずは下記のようにレイアウトをxibファイルで作成します

f:id:kumaskun:20210208225933p:plain

気をつけないといけないのは、デフォルトではiPhoneサイズのViewになっており変更もできないので、sizeをFreeFormにしないといけないということです

そして、Custom Classを対応するクラスに設定しましょう

f:id:kumaskun:20210208230232p:plain

セクションヘッダー(xibファイル)をUITableViewに登録する

override func viewDidLoad() {
  super.viewDidLoad()
  let nib = UINib(nibName: "HeaderView", bundle: nil)
  tableView.register(nib, forHeaderFooterViewReuseIdentifier: "HeaderView")
  tableView.sectionHeaderHeight = 40
}

xibファイルでカスタムし、使用できる状態にします

"HeaderView"の箇所は先ほど作成したクラス名を定義します

tableView.sectionHeaderHeightでセクションヘッダーの高さを一律に設定します

UITableViewDelegateheightForHeaderInSectionでセクション単位で変更する方法もありますが、今回は一律に設定します

セクションヘッダーを独自ビューで生成する

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

        let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderView")

        return view
    }
}

UITableViewDelegateviewForHeaderInSectionで定義します

dequeueReusableHeaderFooterViewを使ってID指定で取り出します。

フッターを作成する場合にはtableView(_:viewForFooterInSection:) -> UIView?で定義します

ヘッダーやフッターをカスタマイズする場合は多いと思うので、参考になれば幸いです!!

エミュレーターでネットに接続する

今回は小ネタです。

以前、エミュレーターでインターネットに接続できずなぜ?と思ったことがあります。

デフォルトでAndroid WifiというWifiに接奥されていますが、設定をしないとインターネットに接続できないようです。 早速みていきましょう!

まずはエミュレーターがあるパスに移動しましょう。

preferenceからAndroid SDKのパスを確認しましょう。

f:id:kumaskun:20201111233451p:plain

ほとんどの場合、下記の場所に置いてあるはずです。

cd /Users/xxx/Library/Android/sdk/emulator

次に使用できるエミュレーターを確認します。

./emulator -list-avds

すると、下記のように一覧が表示されます。

Nexus_5X_API_29
Pixel_2_XL_API_29

そして、下記コマンドで起動するとインターネットに接続できます。 Nexus_5X_API_29の箇所は一覧に表示された名前で変更すると変更したエミュレーターで起動できます。

./emulator -avd Nexus_5X_API_29 -dns-server 8.8.8.8

小ネタですが、1時間くらいハマったので忘れないようにしなきゃと思いました。

Contextについて

今回はAndroidでよく出てくるContextについて説明しようかと思います。

ContextはAndroidでよく出てくると思うのですが、漠然としたものでフワフワしていたので、少し調べてみました。

Contextとは?

Contextを公式で確認すると下記のような記載があります。

Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.

Android OS の環境やリソースへのアクセスの窓口となるコンポーネントです。 データベースやファイル、アプリで利用する画像データなどへのアクセスの窓口となるほか、他のアプリとの連携や、アプリ情報へのアクセスの窓口ともなります。 よく使うのはリソース取得、Activityの起動、intentの送受信などかなあと思います。 また、Context はライフサイクルを持っていて、 端末の様々な 状態遷移の管理も、Contextが行います。

Contextはいくつかの種類が存在します。アプリを作る際には、 どの種類のContext かを意識する必要があります。

Contextの種類

  • Application:Android アプリ全体を統括します。 アプリケーションが起動してから終了するまでのライフサイクルを管理します。シングルトンなので、1つしかないです。
  • Activity:1つの画面を統括します。MVC モデルの、コントローラに相当する役割を担います。 1つの画面が構成され、バックグラウンドに移るか、終了するまでのライフサイクルを管理します。
  • Service:画面を持たず、バックグラウンドで動作します。 バックグラウンドで常に動き続ける常駐型の Service と、都度起動と終了をする Service の2種類があります。 Service が作られてから終了するまでのライフサイクルを管理します。

Contextの取得

Contextを取得することがよくあると思います。大まかには下記のようにして取得します。

  1. Activityのthis
  2. ActivityやApplicationのgetApplicationContext
  3. ViewやFragmentのgetContext

1は特になじみのある方が多いと思います。1はthis@MainActivityのようにすることもできます。

this@にするパターンは下記の例の場合です。

class A {
    inner class B {
        fun Int.foo() {
            val a = this@A
            val b = this@B

            val c = this
            val c1 = this@foo
        }
    }
}

上記の場合はthisはInt foo()のレシーバーであるIntになります。

なので、スコープ外にあるAやBを取得する場合にはthisをつけます。

ここで注目したいのはActivityでContextを取得する場合はthisgetApplicationContextの2つの方法があるということです。

どちらを使っても良さそうで、しかもgetApplicationContextはnullにならないので、ActivityでgetApplicationContextを使おうと思う方もいるかもしれません。

ただ、その場合は意図しない挙動になったり、下手したらメモリリークを引き起こす可能性もあるということを頭に入れておかないといけません。

まず、getApplicationContextの場合はAndroidManifest.xmlに設定したテーマが参照され、thisの場合はAndroidManifest.xmlのに設定したテーマが参照されるかという違いがあります。

そして、常にApplicationContextの方を参照してしまうと意図しないView(標準ダイアログなど)に割り当てられたり、メモリリークの原因となる可能性があります。

QA Stackというサイトに下記のパターン別取得方法が載っていたので、参考になるかもしれません。

f:id:kumaskun:20201108195620p:plain
パターン別Context取得推奨方法

この表をみると基本的にはライフサイクルに従うものはActivityで取得すべきなので、thisを使い、AlpplicationにまたがるものはgetApplicationContextがいい気がします。

例えば、AlertDialogは専用のクラスを作る場合が多いかもしれませんが、表示させるのはActivityでの画面の上なはずです。 そうすると、ライフサイクルに従った方が合理的な印象はあります。

ただ、この辺りは諸説いろいろあるため、いろんな意見を聞いてみたいです!!

自分はこうしているなどあればコメントしてくださると嬉しいです!!

参照

Kotlin リファレンス

getContext()、getApplicationContext()、getBaseContext()と“ this”の違い

Android:引数はthisか?getApplicationContextか?ActivityとApplicationの違い

Xcodeのショートカット集

今回はXcodeのショートカットをまとめようかと思います。

なぜ、この記事を書こうかと思ったのかというと、ショートカットを使うことで開発が効率的に進められることを体感したからです。

最近、ペアプロを体験して、相手の開発をみる機会がありました。 そこで、ショートカットを使うことでこんなにも差があるのかと感じたので、使おうと思いました。

ただ、Xcodeのショートカットをあまり知りませんでした。 むしろ、そんなにショートカットは充実していないと思い込んでいましたが、調べてみるとそれなりに充実していたので、参考用にまとめようと思います。

ビルド・実行のショートカット

結構無意識で使っているコマンドが多いかもしれないですが、載せます。

操作 コマンド
ビルド Cmd + B
実行 Cmd + R
クリーンビルド Cmd + Shift +K
起動しているアプリの終了 Cmd + .
一時停止・再開 Cmd + Control + Y
ブレークポイントの追加・削除 Cmd + \
ステップオーバー F6
ステップイン F7
ステップアウト F8

検索

これは他のIDEでもありますし、よく使う方も多いと思います。

操作 コマンド
ファイル内検索 Cmd + F
フォルダ内検索 Cmd + Shift + O
プロジェクト全体の検索 Cmd + shift + F
ファイル内のメソッド一覧表示と検索 Control + 6

ウィンドウ操作

個人的にはこの辺りはあまり知らなくて、知ってたら1番効率化できそうだと思っている箇所です。

操作 コマンド
ナビゲーターエリア(左側のファイル一覧とか)の表示・非表示 Cmd + 0
ユーティリティエリア(右側の制約を設定したりする箇所)の表示・非表示 Cmd + Option + 0
デバッグエリア(コンソールログとか)の表示・非表示 Cmd + Shift + Y
タブの追加 Cmd + T
左上ナビゲーターエリアのメニューの移動 Cmd + 1 (2,3,4としていくと次のメニューに移動する)

コード編集

実装大好きな人は覚えておいて損はないはず!!

操作 コマンド
選択中のクラス・メソッド・変数の定義へ移動 Cmd + Control + J
自動インデント Control + I
編集を一つ進める Cmd + Shift + Z
インデントを1つ下げる Cmd + [
インデントを1つ上げる Cmd + ]
カーソルを行頭へ移動 Cmd + ⬅ or Control + A
カーソルを行末へ移動 Cmd + ➡ or Control + E
カーソルをファイルの先頭に移動 Cmd + ⬆
カーソルをファイルの最後に移動 Cmd + ⬇
右に一単語 移動 Option + ➡
左に一単語 移動 Option + ⬅
ソースを折りたたむ Cmd + Option + ⬅
折りたたんでいるソースコードを展開 Cmd + Option + ➡
カーソルを行頭へ移動 Cmd + ⬅ or Control + A
カーソルを行末へ移動 Cmd + ➡ or Control + E
現在のカーソル位置から行末をまとめて選択 Cmd + Shift + ➡
現在のカーソル位置から行頭をまとめて選択 Cmd + Shift + ⬅
フォント拡大 Cmd + 「+」
フォント縮小 Cmd + 「-」
選択した変数を一括変更 Cmd + Control + E
クイックリファレンス表示 Option + クリック

シュミレーター

シュミレーターをよく使う方は覚えた方がいいかもしれません。

操作 コマンド
左回りに端末を回転 Cmd + ⬅
右回りに端末を回転 Cmd + ➡
ホームボタンクリック Cmd + H
ロック Cmd + L
キーボードの表示・非表示 Cmd + K
Macキーボードへの切り替え Cmd + shift + K
スローアニメーションモードのオンオフ Cmd + T
マルチタスクへ移動 Cmd + H を2回連続
スクリーンショットの取得 Cmd + S
端末を振る Cmd + control + Z

ここまでいろんなショートカットを記載しました。結構な数ありますが、使いこなすと多くの時間を簡略化できる気がします。 ぜひ、使ってみて効率的な開発ライフをお過ごしください!

追記があれば、追記していきます!!

Optional型について

Swiftだけではないですが、SwiftにはOptional型という特徴的な型があります。

今回はそんなOptional型についてお話しできたらと思います!

Optional型とは

Optional型とは通常の値とは別にnullをとれる型です。 他の言語ではString型やInt型といった通常の型にnullという値が入ると思いますが、Swiftでは通常の型にはNULLを入れることができず、もしnullを扱いたい場合はOptional型を使わないといけません。

SwiftのOptional型はnullの場合に文法的にエラーを返してくれるので、文法通りに書けばバグをある程度回避できるメリットがあります。

Optional型を使おう

では実際にどのようにOptional型を使うかというところを見ていきます。

まず、Optional型はを使います。

宣言時には下記のようにします。

var a: Int?

このように、Intの後ろにをつけるとInt型ではなく、Optional型になります。

また、Int型は初期化しないとコンパイルエラーになりますが、Optional型ではコンパイルエラーにはなりません

let a: Int?  //宣言時に初期値を定義しなければ自動的にnilが入る

var b: Int? = 2 //nilじゃない値も入れることが出来る
b = nil //nilを代入することも出来る

var c: String //nilを入れれないのでエラー

はnullが入る可能性があるという意味なので、Optional型ではnullが許容されています。

ちなみに、Optional型の値を見てみると、下記のようになります。

let a: Int = 1
print(a) // 1

let b: Int? = 2
print(b) // Optional(2)

上記の場合だと、Optional型はInt型ではないので、下記のように計算はできません。

let a: Int = 1
let b: Int? = 2

let c = a + b //コンパイルエラー

では、計算したい場合はどうすればいいでしょうか? そのときに使うのが!です。

let a: Int? = 1
print(a) // Optional(1)
print(a!) // 1

!を使うことをアンラップと言います。このアンラップをすることで、nullが入らないことが前提と定義できるので、上記の場合はOptional型ではなくInt型として使うことができます。

しかし、アンラップしている値にnullが入ったらクラッシュするため注意が必要になります。

let a: Int? = nil
let b = a!  //クラッシュ

クラッシュしないためにnullチェックが必要になりますが、Swiftではif-let構文を使うことが多いです。

let a: Int? = nil
if b = a {
    let d = b!
    print(d) // nullではない場合に通る
} else {
    print("nil") //aがnullの場合の処理
}

上記のようにaがnullの場合はelseの方を通るのでnullの場合の処理を記載することができるので、nullチェックを行うことができます。

また、下記のように変数を使わない場合は_(アンダースコアー)で記載することも可能です。

let a: Int? = nil
if _ = a {
    print("nilではない")
} else {
    print("nil")
}

Swiftを学び始めたときにん??となる箇所だと思いますが、他の言語と比較することで学びやすくなるかと思います。

Comuputedプロパティについて

swiftは他の言語とは少し違い、基本的にはgetter/setterが必要ない言語です。

ただ、設定するときに値を加工したりする場合はComuputedプロパティというものがあります。(名前は最近知りました)

今回はそんなComuputedプロパティについて説明しようかと思います。

Comuputedプロパティとは

Comuputedプロパティを説明する前に他の言語でのgetter/setterはこんな感じではないでしょうか??

例えば、Javaでは下記ですよね!

public class Product {
    private String name;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

public class Phone {

    Product p = new Product();
     
    p.setName("携帯電話");
        
    System.out.println(p.getName()); //携帯電話
}

このようにgetNameメソッドsetNameメソッドを準備して、必要に応じて、値の取得と設定をメソッドを通じて行います。

つまり、決まった値を保持しておらず、インスタンスが生成される度に値を取得、設定することになります。

では、Swiftではどうでしょうか?

class Product {
    var companyName: String
    
    init(companyName) {
        self.companyName = companyName
    }

    var name:String {
        // 値を取得するときに呼ばれる
        get {
            return companyName
        }
        // 値を設定するときに呼ばれる
        set {
            companyName + "の携帯電話"
        }
    }
}

class Phone {

    let p = Product();
    // setが呼ばれる
    p.name = "A社"
    // getが呼ばれる
    println(p.name) //A社の携帯電話
}

上記のnameプロパティに値を設定するときはset、値を取得するときはgetが呼ばれます。

また、getのみを記載すると読み取り専用のプロパティとなります。

上記のようにせず、別で取得用や設定用のメソッドを作れば同じ動きにすることはできますが、変数の定義を見るとすぐにgetter/setterが見れるのは個人的にわかりやすいと思います。

willSet/didSetについて

Swiftではget・set意外にwillSet/didSetというものがあります。

どういものかみていきましょう!

class Staff {
    var salary: Int { 
        willSet {
            print(salary)// 結果 1000
        }
        didSet {
            print(salary)// 結果  1500
        }
    }
    var worked: Int      //働いた時間
    
    init( _ salary: Int) {
        self.salary = salary
    }
}

class Resutaurant {
    let kuma = Staff(1000)
 kuma.salary = 1500
}

流れは今までみてきた例と同じですが、違いはsalarywillSet/didSetが定義されています。

willSetは値が変わる前の処理を書き、didSetは値が変わった後の処理を書きます。

どんなときに使うかというとdidSetは値が変更したらラベルを変更するなどで使うかもしれません。

willSetは使ったことがないのですが、変更前の値を別の変数で保存しておくなどが考えられます。

Comuputedプロパティとは少し違う話にはなってしまいますが、定義の下に処理がかけるのはComuputedプロパティと同じように見やすく使うこともあるかもしれないので、 参考になると幸いです。