diadia

興味があることをやってみる。自分のメモを残しておきます。

Sublime text 3にKotlinのシンタックスハイライトを付ける

参考

https://www.kotlintips.com/kotlin-support-in-sublime-text/

やり方

  1. Toolsの Install Package Controlを選択する。 *最初の状態のSublime Text 3ではInstall Packageコマンドが使えない。これを使えるようにするために行う。

  2. Control + Shift + Pを押して表示されるフォームにInstall Packageを入力する。

  3. Package Control: Install Packageが出てくるで、それを選択する

  4. Kotlinを入力して出てきたKotlinを選択する。

これでオッケイ!

日付データをバリデーションする方法

入力された日付データをバリデーションする方法を記述する。

バリデーションする内容:現実に存在しない日付データにはfalseを返す。

fun checkInputDate(inputDate: String) :Boolean{
    try{
    val format = SimpleDateFormat("yyyy/MM/dd")
    // SimpleDateFormat.isLenientをfalseにすると現実に存在する日付かをバリデーションする仕様に変化する
    format.isLenient = false
    format.parse(inputDate)
    }catch (e: ParseException){
        return false
    }
    return true
}

フラグメントでオプションメニューを実装する

参考

FragmentでActionBarを指定したい! - Qiita

実装の概要と注意点

編集するファイルは、フラグメントが紐付けられるアクティビティ.ktファイルとフラグメント.ktファイルである。 両ファイルともにonCreateOptionsMenuをオーバーライドする。またフラグメント.ktではsetHasOptionsMenu(true)を記述する必要がある。

ちなみにアクティビティ.ktにオーバーライドしたonCreateOptionsMenuを実装しないとエラーが生じる。

その記事は以下である。

エラー対処:java.lang.IllegalStateException: menu.findItem(R.id.action_settings) must not be null - diadia

実装手順

アクティビティ.ktの実装

  1. onCreateOptionsMenuのオーバーライドで、表示するオプションを決定する

フラグメント.ktの実装

  1. onCreateViewの中のsetHasOptionsMenu(true)を選択
  2. onCreateOptionsMenuのオーバーライドで、表示するオプションを決定する
  3. onOptionsItemSelectedでメニューの処理を設定

setHasOptionsMenu(true)に関する資料(ドキュメント)

Fragment  |  Android Developers

EditTextをマテリアルデザイン化する

https://developer.android.com/reference/com/google/android/material/textfield/TextInputLayout

https://developers-jp.googleblog.com/2015/07/android-design-support-library.html

要はTextInputLayoutビューを実装すれマテリアルデザイン化することができる。

当該ビューの属性を設定することで色々な機能をもたせることは分かった。

TextInputLayoutのプレイスホルダーに関して

これについてはEditTextでプレイスホルダーを設定するのと同じ様に、hint属性にセットすればよい。

TextInputLayoutをでバリデーションを行う

つまりはエラーを表示させたいわけだけれども、それには

Showing an error via setErrorEnabled(boolean) and setError(CharSequence), along with showing an error icon via setErrorIconDrawable(Drawable)

と書いてあるとおり、上記を利用すれば良い。

RecyclerViewまとめ

メモ

今までEmptyActivityからリサイクラービューを実装してきたが、フラグメントからリサイクラービューを実装する方法の方が割と楽に実装できることが分かった。これについてはまだブログに記録を残していないので時間があるときに作成する。

インストール方法

gradleのdependenciesに以下を追加する。

implementation "androidx.recyclerview:recyclerview:1.1.0"

バージョンの確認については以下をみて確認すれば良い。

Recyclerview  |  Android デベロッパー  |  Android Developers

サンプルコード

retrofit, backendにdjangoをを使用

RecyclerViewSample/app/src/main/java/com/example/recyclerviewsample at master · chiaki1990/RecyclerViewSample · GitHub

リサイクラービューにリスナーをセット

リサイクラービューではOnClickListenerをセットすることはViewHolderの継承でインターフェースにOnClickListenerを実装すればよい。

リサイクラービューでOnItemClickListenerを実装するほう法がわからん。 https://gist.github.com/arcadefire/1e3a95314fdbdd78fb211b099d6ec9da

画面表示の際に下部の要素を表示する

参考:RecyclerViewを下から表示するにはstackFromEnd = trueを入れる · GitHub layoutmanagerの要素としてstackFromEnd = trueを追加すればい。

val layoutManager = LinearLayoutManager(context).apply{
    stackFromEnd = true
}

django-rest-authで"detail": "Authentication credentials were not provided."が返される時

参考

Django Rest Framework - Authentication credentials were not provided - Article - Python Decompiler Online

エラーメッセージ

   {"detail": "Authentication credentials were not provided."}

エラーに出くわした状況

rest-authのエンドポイント/rest-auth/password/change/でパスワード変更を試みたが上記のレスポンスが返ってきた。 リクエストはcurlコマンドで送っており、ヘッダーにトークンを添付しているにも関わらず、認証に失敗している状況である。

curl -X POST http://127.0.0.1:8000/rest-auth/password/change/ -d 'new_password1=12345tweet&new_password2=12345tweet&old_password=1234tweet' -H 'Authorization: Token ebfa00bd84de2b8f319b747636270257ec24601c'

解決方法

参考のページと全く同じ方法で解決した。 かんたんに言えば、djangoのsettings.pyにおいてREST_FRAMEWORKを設定することだった。自分の場合は各APIViewのサブクラスの属性情報としてパーミッションの記述や認証の記述を書いて開発してきた。しかしながらdjango-rest-authの場合は認証やパーミッションについて設定されていない。したがってヘッダーにトークンを添付しても認証が行われなかった。 django-rest-authに認証方法を設定するために以下の記述をするとトークンによる認証もうまくいった。

########################
###Django-Rest-Framework###
########################


REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
           'rest_framework.authentication.TokenAuthentication',
           'rest_framework.authentication.BasicAuthentication',
       ),
       'DEFAULT_PERMISSION_CLASSES': (
            # https://www.django-rest-framework.org/api-guide/permissions/#setting-the-permission-policy
            #'rest_framework.permissions.IsAdminUser',
            #'rest_framework.permissions.IsAuthenticated',
            #'rest_framework.permissions.AllowAny',
       ),
  }

関連記事

djangoにRESTAPIを実装する メモ - diadia

EditTextにplaceholderを設ける

参考

How do I set a placeholder on Edittext in Android and use it if no input has been typed by user? - Stack Overflow

EditTextで、ヒント文字列を設定する | mokelab tech sheets

Android開発ではHint属性と呼ぶ

HTMLで使われるplaceholder属性は、android開発においてEditTextのHint属性にあたる。 したがってplaceholderのような機能を実装したければ、hint属性にテキストを入力すれば良い。

f:id:torajirousan:20200309084002p:plain

関連記事

EditTextまとめ - diadia

DRF PATCHの実装方法

参考

Retrofit 2 — How to Update Objects on the Server (PUT vs. PATCH)

How to make a PATCH request using DJANGO REST framework - Stack Overflow

Serializers - Django REST framework

まずPATCHとは

パッチはインスタンスの更新を行うHTTPプロトコルのメソッドである。インスタンスの更新はPUTとPATCHの2つの方法が存在する。 PUTはインスタンスすべてを書き換えることで、PATCHはインスタンスの一部を書き換えることのようだ。

やり方イメージ

Django Rest Frameworkにはシリアライザがある。シリアライザに変更したい内容、変更したいインスタンス、変更は一部のみを明示するpartial=Trueをシリアライザの引数として渡すことでPATCHが実現される。

Serializers - Django REST framework

もちろん、APIViewのメソッドとしてpatchを宣言するし、シリアライザの結果にたいしてsave()メソッドを実行することは言うまでもない。

サンプル

from .utils import getTokenFromHeader
from .utils import getUserByToken


class ProfileAPIView(APIView):

    authentication_classes = (TokenAuthentication,)
    permission_classes = (permissions.IsAuthenticated,)
    
    def patch(self, request, *args, **kwargs):
        #profileオブジェクトを更新する

        token = getTokenFromHeader(self)
        user_obj = getUserByToken(token)
        profile_obj = Profile.objects.get(user=user_obj)
        serializer = ProfileSerializer(profile_obj, data=request.data, partial=True)
        serializer.save()
        return Response({"result": "success"})

関連記事

エラーが出てpatchを実行できないパターン

AssertionError: The `.update()` method does not support writable nested fields by default.
Write an explicit `.update()` method

この場合はあるモデルを表すJSONの内部に更に他のモデルがネストされている場合にエラーが生じると考えられる。

DRF tokenからuserオブジェクトを取得する

userオブジェクトを取得する道筋

例えばandroidの画面にuserのプロフィールを表示したいとする。そのためには、userオブジェクトが必要だ。 どの様にすればUserオブジェクトが取得できるのか。

TokenAuthenticationを採用している場合にはユーザーに紐付いたトークンがデータベースに保存してある。 したがってトークンを利用すればUserオブジェクトを取得できる。

では、トークンはどうすればサーバー側(django側)で取得できるのか。 トークンはHTTPリクエストのヘッダーに添付されて送られる。 したがってリクエストのヘッダーからトークン情報をとってくれば良い。

サンプルコード

from rest_framework.authtoken.models import Token

class ProfileAPIView(APIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = (permissions.IsAuthenticated,)

    def get(self, request, *args, **kwargs):
        #HTTPリクエストヘッダーのトークン情報からユーザーを特定する
        token = self.request.META['HTTP_AUTHORIZATION'].split(" ")[1]
        #print(token)
        #Userオブジェクトの取得
        user_obj = Token.objects.get(key=token).user
        profile_obj = Profile.objects.get(user=user_obj)

        serializer_context = {}
        serializer_context["profile_obj"] = ProfileSerializer(profile_obj).data
        return Response(serializer_context)

解説

httpプロトコルのヘッダー情報は、request.METAから取得することができる。 リクエストヘッダの内容を取得する まとめ - diadia

rest_framework.authtoken.models.Tokenクラスがある。 これを利用するとUserオブジェクトを直接取得できる。 参考: python - Get user object from token string in DRF? - Stack Overflow

retrofit multipart送信を行う

コンテンツ

  1. Multipart通信における知見
  2. 関連記事
  3. 参考

Multipart通信における知見

実装概要

実装ではInterfaceの設定とretrofitでinterfaceメソッド実行を行う。

Interfaceについて

    //Itemオブジェクトを生成する
    //認証ユーザーのみリクエスト送れる仕組みが必要
    //@Headers("Content-Type:application/json")
    @Multipart
    @POST("api/item_create_1/")
    fun postItemCreateAPIViewMultiPart(
        @Header("Authorization") authTokenHeader: String, 
        @Part file:MultipartBody.Part,
        @Part("testpart") requestBody: RequestBody
    ):Call<ResultModel>

@Partには2つの使い方がある。 Partに付随するのはMultipartBody.Partであるか、RequestBodyであるかである。

テキストデータを送信する場合には、 RequestBodyオブジェクトを使う。 画像を送信する場合には、画像ファイルをラッピングしたMultipartBody.Partを使う。

RequestBodyオブジェクトを生成する

val reqBody :RequestBody = RequestBody.create(MediaType.parse("application/json"), itemObj.toString())
RequestBodyクラスについて

RequestBody (OkHttp 3.14.0 API)

RequestBodyクラスはOkHttp3のクラスである。

returns method
static RequestBody create(MediaType contentType, String content)
static RequestBody create(MediaType contentType, File file)

RequestBodyにはstaticメソッドのcreateがある。 このメソッドの第1引数は MediaType オブジェクトである。 第2引数はいろいろ種類がある。 自分が使うものとして、FileオブジェクトとStirngオブジェクトがある。画像を送信したい場合には画像のFileオブジェクトを渡す。テキストデータの場合はStringオブジェクトを渡す。

このインスタンスは、テキストデータならこのまま送れるし、画像データならこれを核にしてMultipartBody.Partオブジェクトを生成する。

var strContent = "{ 'item' : {'id':3, 'title':'医学書', 'author':'ドクター'} }"
val mediaType = MediaType.parse("application/json")
val reqBody = RequestBody.create(mediaType, strContent)

MultipartBody.Partオブジェクトを生成する方法

MultipartBodyクラスはRequestBodyクラスのサブクラスである。

retruns method
static MultipartBody.Part createFormData(String name, String filename, RequestBody body)

画像を送信するためにMultipartBody.PartのcreateFormData()メソッドを使う。

val filepath = "hoge"
val imageFile = File(filePath)

val mediaType = MediaType.parse("image/*")
val reqBody = RequestBody.create(mediaType, imageFile)
val part = MultipartBody.Part.createFormData("part_name", imageFile.name, reqBody)

memo

上記のインターフェースとオブジェクトを組み合わせてmultipart通信を実行する。

疑問点

関連記事

Retrofitまとめ - diadia

参考

Android Image Upload Example | Multipart Retrofit2

retrofitでマルチパートなものをPOSTする - tumblr

Upload file in Android Java with retrofit 2 - Vo Thanh Tung - Medium

Android Image Upload Example | Multipart Retrofit2

curlコマンドについて

まず以下のコマンドを理解する

curl -X GET http://127.0.0.1:8000/api/example/ -H 'Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b'

これの意味を調べる。

参考:

https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication

https://www.setouchino.cloud/blogs/99#post-x-post-d

-XオプションはHTTPメソッドを定めるものである。-Xの値はPOST や GETである。

-Hオプションはリクエストヘッダーに格納したい場合に使うものである。値がヘッダーに格納される。

-d (--data)オプションはデータを送る時に使うものでる。例えばwebサービスでnameというフォームが有り、"hajime"と入力し送信する場合には、curlでは以下のように送信されることになる。

curl -X POST -d 'name=hajime' example.com/send_info

データが複数ある場合には以下のように複数の方法で送信できる。

#①データとデータの間を&amp;でつなぐ方法
curl -X POST -d 'name=hajime&age=23' example.com/send_info/

#②各データを-dオプションを使う方法
curl -X POST -d name=hajime -d age=23 example.com/send_info/

-dオプションの参考url: https://ec.haxx.se/http/http-post

JSONcurlコマンドで送信する方法はどうすれば良いか?

curljsonを送信する際に考えなければならない要素は2つある。

  1. -d オプションで付すデータの形式を変更する
  2. json形式のデータであることを明示するためにheaderに情報を追加する

-d オプションのデータ形式

まずデータをシングルクオテーションでくくる。そして肝心のデータはJSON形式で記述する。 したがって以下のような形式で記述する。

curl -X POST -d 'JSON_data_forma' https://hogegoe.com/api/

具体的には、

curl -X POST -d '{"json1":"value1", "json2": "value2"}' https://hogegoe.com/api/

headerに情報追加

ヘッダーに情報を追加するには、-H オプションを使う。

JSONデータ形式を明示するには-Hオプションの値として'Content-Type:application/json'を使う。

curl -X POST -d '{"json1":"value1", "json2": "value2"}' https://hogegoe.com/api/ -H 'Content-Type:application/json

https://developer.mozilla.org/ja/docs/Web/HTTP/Headers

https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Type