diadia

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

DRF POSTメソッドのデータの取り扱いについて

どうデータを受け取るか

POSTメソッドを伴うエンドポイントがあるとして、weアプリケーションはどの様にデータを受け取ればよいのか。

Djangoの機能で処理する方法

Djangoと同じ様にデータを受け取る方法がある。 つまりself.request.POSTの戻り値にPOSTメソッドによって送信されたデータが格納されている。

class ContactAPIView(APIView):
    #endpoint: "api/contacts/"

    def post(self, request, *args, **kwargs):
        print(self.request.POST)
     # self.request.POSTのデータを使ってobjectを生成、変更するコードを記述
        return Response({"hoge", "HOGE"})

Django Rest Framework(DRF)の機能で処理する方法

Requests - Django REST framework

class ContactAPIView(APIView):
    #endpoint: "api/contacts/"

    def post(self, request, *args, **kwargs):
        print(request.data)
     #request.dataをシリアライザを使いデシリアライズ化させるコード記述
        return Response({"hoge", "HOGE"})

retrofit Requestヘッダーを参照する方法

例えばRequestヘッダーのAuthorizationという項目がきちんと送信できているか確認したいとする。

retrofitでは以下の様ににすればヘッダーの内容を確認することができる。

fun logInByAuthToken(authToken:String) {

  val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build()

  val service = retrofit.create(APIService::class.java) 
  service.loginWithAuthtoken(authToken).enqueue(object: Callback<AuthModel>{

    override fun onResponse(call: Call<AuthModel>, response: Response<AuthModel>) {
      println(response.headers()) //responseのheaderを参照
      println(call.request().headers()) //requestのheaderを参照
      if (response.isSuccessful) {
          ...省略...            
      }else if(response.isSuccessful == false) {
          ...省略...
      }
    }

    override fun onFailure(call: Call<AuthModel>, t: Throwable) {
         ...省略...
    }
}

解説

onResponseメソッドの引数にcall引数とresponse引数がある。これはそれぞれrequestとresponseにあたる。 requestはCallクラスなので、Callクラスのメソッド、属性を確認するとrequest()メソッドがある。 このメソッドの返り値はokhttp3.Requestクラスインスタンスである。このインスタンスにheaders()メソッドを用いるとヘッダー情報を取り出すことができる。

Retrofit 2.7.1 API

関連記事

Retrofit - diadia

SharedPreferencesのxmlファイルを直接見る

参考記事

SharedPreferencesに出力したデータの確認

概要とやり方

SharedPreferencesのxmlファイルは、まずプロジェクトのソースコード郡に生成されるものではない。 したがってandroid studioのprojectから探そうとしてもないのである。

では実際にどこにあるかというと、android端末に保存されている。android端末はエミュレーターの場合android studioから見ることができる。 以下はSharedPreferencesファイルを見るためにエミュレータの内部ファイルを見る方法で試みている。

やり方

  1. android studioのviewメニューからTool Windowsを選択する。
  2. 選択するとDevice File Explorerが選択肢にあるのでそれを選択する。
  3. /data/data/[android project名]を探す。
  4. shared_prefsディレクトリ内のファイルを見る。

関連記事

SharedPreferencesの使い方 - diadia

JetPack Securityについて調べる

参考ページ

https://android-developers.googleblog.com/2020/02/data-encryption-on-android-with-jetpack.html

https://qiita.com/MasayukiSuda/items/61b554d3cbe484f13706

https://www.bignerdranch.com/blog/encrypting-shared-preferences-with-the-androidx-security-library/

インストール

https://developer.android.com/jetpack/androidx/releases/security#declaring_dependencies

implementation "androidx.security:security-crypto:1.0.0-alpha02"

実装サンプル

jetpack securityはファイルを暗号化する機能とSharedPreferenceの値を暗号化する機能を提供している。 OAuthトークンやAPIキー等の秘密情報を保存することに使われ、それらのデータをローカルで保存することになる。

val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)

val sharedPreferences = EncryptedSharedPreferences.create(
    "my_secret_prefs",
    masterKeyAlias,
    applicationContext,
    PrefKeyEncryptionScheme.AES256_SIV,
    PrefValueEncryptionScheme.AES256_GCM
)


// storing a value
sharedPreferences
    .edit()
    .putString("some_key", "some_data")
    .apply()


// reading a value
sharedPreferences.getString("some_key", "some_default_value") // -> "some_data"

リンク

SharedPreferencesの使い方

ListView,Spinnerとadapterについて

ListViewとadapterの関係

ListViewの各要素を表示させるには、strings.xml

<string-array name="japanese">
<item>あいうえお</item>
<item>かきくけこ</item>
<item>さしすせそ</item>
</string-array>

を記述して、レイアウトファイルにandroid:text="japanese"みたいなことをして各要素を表示する事ができる。

この他に要素を表示する方法としてadapterがある。これは.ktファイル内に要素を記述してその内容を表示するものである。

adapterの概念

Adapterは配列またはデータベースなどからそれぞれの要素をリストに入れられるように変換し自動的に挿入してくれます。

他に必要な引数は別において、共通的な考え方には以下がある。

adapterの引数にリスト様式のデータとレイアウトを引数としてadapterのインスタンスを生成する。このapapterをlistviewのプロパティであるadapterでつなげて作成する。

ArrayAdapter

sample

val lv_object = findViewById<ListView>(R.id.hoge)
val list_data = arrayOf("寿司","ラーメン", "カレー")
val adapter = ArrayAdapter(applicationContext, android.R.layout.simple_list_item_1, list_data)

lv_object.adapter = adapter

https://developer.android.com/reference/kotlin/android/widget/ArrayAdapter ArrayAdapterはListViewやSpinnerを使うときに使われるようだ。

SimpleAdapter

simpleadapterはmapのリスト様式データを使う。

adapterとレイアウトの関係について

レイアウトファイルはandroid studioビルトインのファイルと自作ファイルの2種類がある。そこで自分がどのレイアウトファイルを使うかを選ぶ必要がある。自分がどのレイアウトファイルを使用するかの宣言はadapterのコンストラクタで行う。具体的にはArrayAdapter, SimpleAdapterの引数の一つとしてR値でレイアウトファイルを記述する。また記述も多少違いがある。

自作したファイルを宣言する場合はR.id.ファイル名

ビルトインファイルを宣言する場合はandroid.R.id.ファイル名

このRの前にandroidをつけるか否かは、このように考えれば良い。Rはresourceつまりresを表す。res以下には、drawableやlayoutフォルダがある。だからR.drawable.hoge, R.layout.fooと書く。Rから書く場合は自分のファイルをR値として宣言する。アンドロイドビルトインのファイルを使う場合はそれを示すandroid.R...と続けて書く。 https://developer.android.com/guide/topics/resources/accessing-resources.html?hl=ja#%E6%A7%8B%E6%96%87

string-arrayを元にしたadapterオブジェクトの生成方法

spPais.adapter = ArrayAdapter<String>(
       MyApplication.appContext,
       android.R.layout.simple_spinner_item,
       paisItems)

paisItemsはArrayList, Array, Listである。R.stringsファイルに定めたデータからリストを作成する方法は以下となる。

val adapter = ArrayAdapter.createFromResource(Contextオブジェクト, R.array.paisList, android.R.layout.simple_list_item_1)

したがってadapterをspinnerにセットする方法は

fun setAdapterToPaisSpinner(spinner: Spinner){
    // R.id.spSelectPais用の関数
    val adapter = ArrayAdapter.createFromResource(MyApplication.appContext, R.array.paisList, android.R.layout.simple_list_item_1)
    spinner.adapter = adapter
}

AccountManagerでアカウントを追加する

アカウントを追加

AccountManagerにはアカウントを追加する方法が2種類存在する。

メソッドは以下の通り。

  1. AccountManager#addAccountメソッド
  2. AccountManager#addAccountExplicitlyメソッド

addAccount()メソッドは、アカウントを追加する時にユーザーにアカウント情報を入力させて追加する方法である。 一方addAccountExplicitly()メソッドは、アカウント追加をユーザーに関係なく追加する。

https://developer.android.com/reference/kotlin/android/accounts/AccountManager#addAccountExplicitly(android.accounts.Account,%20kotlin.String,%20android.os.Bundle)

django-rest-frameworkのユーザー認証を実現させる

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

https://blog.sakaki333.com/blog/view/78

はじめに

この記事はdjnagoをバックエンドとしてandroidアプリケーションを作成することを前提する。 androidアプリケーションのユーザー認証周りについてどのように実装していけばよいかについて書く。djangoをバックエンドにandroidのアプリを作成する記事をあまり見つけることができなかったのでこの記事が誰かの力になれば幸いである。 構成としては、djangoの認証周りの設定を記述したあとに、androidの実装について記述する。

Basic認証のサンプル

from rest_framework.views import APIView
from items.models import Item
from .serializers import ItemSerializer
from .serializers import ItemSerializer
from rest_framework.authentication import BasicAuthentication
from rest_framework import permissions



class TestAPIView(APIView):

    authentication_classes = (BasicAuthentication,)
    permission_classes = (permissions.IsAuthenticated,)

    def get(self, request, *args, **kwargs):
        

        item_objects = Item.objects.all()
        serializer = ItemSerializer(item_objects, many=True)
        return Response(serializer.data)

TestApiViewにrequestを送ると、データが表示されずにログインフォームが表示される。

これはauthentication_classes = (BasicAuthentication,)を設定することでログインフォームが表示されたのである。

ちなみにauthentication_classes = (TokenAuthentication,)とすればTokenによる認証に変更することもできる。これについては後述する。

androidアプリケーションで使用する場合には、毎回フォームに認証情報をPOSTする使い方をすればコンテンツを表示できると思われる。

https://grandbig.github.io/blog/2016/02/07/android-login-activity/

時間がある時にこの続きを書く。

別の方法でユーザー認証を実現させる

django-rest-authを利用して認証周りを実装する

まずユーザー周りのエンドポイントを実装する

https://django-rest-auth.readthedocs.io/en/latest/index.html

上記はdjango-rest-framwork用のユーザー認証等を実現させるためにあるライブラリである。django-allauthのようにpipでインストールするとエンドポイントが自動的に決定される。このdjango-rest-authで決められたエンドポイントにアクセスすることで、ログイン認証ができたり、ユーザー登録など実装の手間を省くことができる。

django-rest-authでエンドポイントを設定したら後はアンドロイドアプリで指定のエンドポイントを記述すれば認証周りの仕組みを実装できそうだ。 具体的な例として、ユーザー登録の実装方法を考える。

f:id:torajirousan:20200125172645p:plain

http://localhost:8000/rest-auth/registration/にアクセスすると上記の画面を表示する事ができる。(django-rest-authをインストールすることが前提である。) 画像下部のUsername, Email, Password1, Password2フォームを埋めてPOSTボタンを押すとトークンが返ってくる。

f:id:torajirousan:20200125173600p:plain

送信内容に誤りがなければ、トークンが返されることを上記の画像で確認できる。このトークンを使って次回からログイン認証を行うことになる。

ちなみにdjango-rest-frameworkにはベーシック認証とトークンを使った認証が準備されており、この例ではトークンを使った認証を採用している。

参考:https://www.django-rest-framework.org/api-guide/authentication/#api-reference

このトークン認証の設定にはINSTALLED_APPSに'rest_framework.authtoken'を追加することと、settings.pyにTokenAuthenticationを追加すれば良い。

これらを確認できたらユーザー認証周りのエンドポイントは準備できたことになる。

エンドポイントにブラウザではない方法でHTTPで接続してみる

先程作成したユーザー登録するエンドポイント/rest-auth/registration/にはKotlinでアクセスすることになる。だからdjango-rest-frameworkのユーザーインターフェースを使わず、純粋なHTTPプロトコルでユーザー登録ができるか確認するべきである。その手段としてcurlを用いる。

curlについては必要最低限ここにまとめた。

curl -X POST -d 'username=&amp;email=testmyga@gmail.com&amp;password1=1234tweet&amp;password2=1234tweet' http://127.0.0.1:8000/rest-auth/registration/ 

上記のように入力すると、ターミナルには{"key":"b2fa2985300735de965fe2ec80cb0247e2f7bccf"}のような形で返される。このように返されたらHTTPプロトコルでユーザー登録を実行することができた証拠なので、この形式をKotlinのアクティビティ(.ktファイル)で記述すれば良いと判断できる。なお、curlのコマンドの例ではusernameを入力不要の設定にしてる。

具体的に実装するには

ログインのエンドポイントまたはユーザー登録のエンドポイントでTokenを取得する。

//あるクラスのメソッド
    fun logInByBasicAuth() {

        val retrofit = Retrofit.Builder()
            .baseUrl("http://10.0.2.2:8000/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        val service = retrofit.create(APIService::class.java)
        val response = service.login(email, password).enqueue(CustomCallBack())
    }



class CustomCallBack : Callback<LoginResultModel> {
    //BasicAuthにおいて認証データが適切な場合にはステータス:200が返される。
    //逆に不適切な場合にはステータス:400が返される。
    //したがって認証情報が不適切な場合にはnon_field_errorsが返されることは全くない

    override fun onResponse(call: Call<LoginResultModel>, response: Response<LoginResultModel>) {
        print("レスポンスを回収")
        println(response.code())
        if (response.code() == 200) {
            //println(response.body()?.key)
            authToken = response.body()?.key


        }else if(response.code() == 400) {
            println(response.message())
            }
    }
    override fun onFailure(call: Call<LoginResultModel>, t: Throwable) {
        //TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
        println("エラーハンドリング")
    }
}

//
interface APIService {
    @FormUrlEncoded
    @POST("rest-auth/login/")
    fun login(@Field("email") email: String?, @Field("password") password: String?) : Call<LoginResultModel>
}

このトークンを何らかの形で保存する。自分の場合はAccountManagerクラスのsetAuthTokenメソッドでTokenを保存する。

次回からこのTokenをhttpプロトコルのヘッダーに添付して通信を行えば実装できる。

retrofit エラー対処

エラー1

java.lang.IllegalArgumentException: @Field parameters can only be used with form encoding.

上記のエラーメッセージが出る場合には、インターフェースに@FormUrlEncodedアノテーションを付け忘れている可能性がある。 つけて再実行する。

interface ShareService {
    @FormUrlEncoded
    @POST("rest-auth/login/")
    fun login(@Field("email") email: String?, @Field("password") password: String?) : Call<AuthModel>
}

エラー2

java.net.UnknownServiceException: CLEARTEXT communication to 10.0.2.2 not permitted by network security policy

マニフェストファイルにhttp通信を行うパーミッションを記述していないことから生じるエラーである。 参考:https://stackoverflow.com/questions/45940861/android-8-cleartext-http-traffic-not-permitted

<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        ...
        android:usesCleartextTraffic="true"
        ...>
        ...
    </application>
</manifest>

Retrofitまとめ

コンテンツ

  1. 公式ドキュメント
  2. インストール
  3. 使い方イメージ
  4. 関連記事
  5. 参考

1. 公式ドキュメント

https://square.github.io/retrofit/

2. インストール

implementation 'com.squareup.retrofit2:retrofit:(insert latest version)'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.8.1'

3. 使い方イメージ

  1. インターフェースを定義する。
  2. retrofitインスタンスを生成する。create()メソッドの引数をインターフェースとする。
  3. HTTP通信を実行する
1.インターフェースを定義する
interface MyApi{
    @GET("/my/end/point")
    fun getItemList(): Call<MyResponse>
}
#インターフェースの補足

Interfaceでエンドポイント、メソッド、パラメーターを定義する。

2.retrofitインスタンスを生成する。create()メソッドの引数をインターフェースとする。
val retrofit:Retrofit = Retrofit.Builder()
    .baseUrl("https://example.com/")
    .build();

val service = retrofit.create(MyApi::class.java);

なお、POSTメソッドを行う場合にはretrofitインスタンスにretrofit#addConverterFactory()メソッドを追加する必要があるようだ。

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

val service = retrofit.create(MyApi::class.java);

参考:Retrofit2 &OkHttp でAndroidのHTTP通信が快適だにゃん

3.HTTP通信を実行する

retrofitインスタンス設定したインターフェースのメソッドを呼び出す。その後にexecute()またはenque()メソッドを加えるとメソッドが実行される。 executeメソッドは同期処理。enqueメソッドは非同期処理。

service.getItemList().execute( 省略 )
//または
service.getItemList()enque({ 省略 })

4. 関連記事

retrofit Requestヘッダーを参照する方法 - diadia

retrofit multipart送信を行う - diadia

retrofitでmultipart通信 複数の画像を送信する - diadia

5. 参考

https://www.tsurutan.com/entry/2016/10/04/003929

http://pppurple.hatenablog.com/entry/2018/06/30/234400

RecyclerViewについて

RecyclerViewはなんのためにあるのか?

参考:

https://ithelp.ithome.com.tw/articles/10203735 https://developer.android.com/guide/topics/ui/layout/recyclerview

RecyclerViewは多量のリストデータセットを表示するためにあるようだ。

リサイクラービューで必要な要素

ViewHolderが必要らしい。これが何であるかわかっていない。
ただViewHolderはreturnさせる対象であることがわかった。onCreateViewHolderをオーバーライドしてViewHolderをリターンする。

RecyclerView.ViewHolderを継承して作るクラスには、作成したテンプレートに対応する変数を定義する。ちなみにViewHolderは、ViewHolder.ktみたいな形で定義するのが一般的なようだ。
参考:
https://qiita.com/Todate/items/297bc3e4d0f3d2477ed3#viewholder%E3%82%92%E4%BD%9C%E6%88%90
https://qiita.com/saiki-ii/items/78ed73134784f3e5db7e#2viewholder%E3%82%92%E4%BD%9C%E3%82%8B

RecyclerView自体の表示は、指定したレイアウトマネージャーから提供されるビューを使用する。(LinearLayoutManager, GridLayoutManager)
mainRecyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)

ビューホルダーがコンテンツ表示を担当する。

ビューホルダー オブジェクトはアダプターによって管理されます。ビューホルダーオブジェクトはアダプターによって生成され、アダプターによってビューホルダーのデータバインドも行われる。バインドは、アダプターの onBindViewHolder() メソッドを呼び出すことによって行います。

まとめ

onCreate上にrecyclerviewオブジェクトを取得し、applyメソッドを実施する。するとrecyclerviewを起動させることができる。

override fun onCreate{

var recyclerview = findViewById<RecyclerView>(R.id.my_recycler_view).apply


}

applyについてはこれがわかりやすいい。
参考:https://qiita.com/wakwak/items/7f576b4dbada0995f069
クラスのインスタンス生成するときに属性を設定したい場合がある。この時に"インスタンス.属性=属性値"としなくても設定できるものだと理解した。今はオブジェクト生成時に属性値を定めるためにあると思われる。

実装の概要

とりあえず、onCreateメソッドのところにRecyclerViewオブジェクトを取得する。このrecyclerViewオブジェクトを設定する際にrecyceler.Adapterオブジェクトを継承したクラスのインスタンスを生成する。

Adapterクラスでは4つのことを決める。

  1. viewHolderクラスを継承したクラスの定義
  2. onCreateViewHolderメソッドのオーバーライド
  3. onBindViewHolderメソッドのオーバーライド
  4. getItemCountメソッドのオーバーライド

各メソッドで記述することは以下の通りである。

onCreateViewHolderでは、使いたいレイアウトをインフレイトする。インフレイトしたオブジェクトを引数にViewHolderインスタンスを生成しそれを戻り値とする。 onBindViewHolderメソッドでは、生成したViewholderインスタンスとデータの繋がり方を設定する。

具体的な例

表示する内容は一つのリストに、画像とタイトルと詳細を表示する。 まずレイアウトファイルを作成する。レイアウトファイルには、RecyclerViewが記述された親レイアウトファイル(activity_main.xml)とrecyclerviewの内容である子レイアウトファイル(list_item.xml)がある。

...activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>
...list_item.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">


    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:src="@drawable/default_item"/>


    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/row_title"
            android:layout_width="wrap_content"
            android:layout_height="0dp"
            android:layout_weight="0.5"
            android:text="タイトルを表示" />

        <TextView
            android:id="@+id/row_detail"
            android:layout_width="wrap_content"
            android:layout_height="0dp"
            android:layout_weight="0.5"
            android:text="詳細情報を表示" />


    </LinearLayout>

onCreateメソッドでrecyclerViewオブジェクトを取得するコードを記述する。

...MainActivity.kt

package com.example.practice_recycler_view

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // CreateDataset()ファンクションでデータを生成する
        val myDataset = createDataset()

        // recyclerviewオブジェクトのapplyの要素の一つを生成する
        var viewManager = LinearLayoutManager(this)
        // recyclerviewオブジェクトのapplyの要素の一つを生成する
        var viewAdapter = MyAdapter(myDataset)

        //recyclerviewを取得
        var recyclerView = findViewById<recyclerview>(R.id.my_recycler_view).apply {
            // use this setting to improve performance if you know that changes
            // in content do not change the layout size of the RecyclerView
            setHasFixedSize(true)

            // use a linear layout manager
            layoutManager = viewManager

            // specify an viewAdapter (see also next example)
            adapter = viewAdapter
        }
    }    
    
</recyclerview>

viewHolderクラスを定義する。当該クラスはRecyclerView.Adapterクラスを継承した中で定義を行う。

...MainActivity.ky


    // MyAdapterに渡す引数は実際表示するデータである。
    // 継承するAdapterのタイプにはViewHolderを記述する。
    class MyAdapter(private val myDataset: List&lt;RowModel&gt;) :
        RecyclerView.Adapter&lt;MyAdapter.MyViewHolder&gt;() {


        // viewHolderを定義する
        class MyViewHolder(val view: View) : RecyclerView.ViewHolder(view){
            val row_title  = view.findViewById&lt;TextView&gt;(R.id.row_title)
            val row_detail = view.findViewById&lt;TextView&gt;(R.id.row_detail)
        }

onCreateViewHolderメソッドを記述する。当該メソッドはRecyclerView.Adapterクラスを継承した中でオーバーライドを行う。

...MainActivity.kt

        override fun onCreateViewHolder(parent: ViewGroup,
                                        viewType: Int): MyAdapter.MyViewHolder {
            // レイアウトファイルをインフレイトしてオブジェクトを生成する。
            // 第一引数はインフレイトしたいレイアウトファイル
            val rowView = LayoutInflater.from(parent.context)
                .inflate(R.layout.list_item, parent, false)

            // 定義したViewHolderにインフレイトしたオブジェクトを引数として渡す。
            // 戻り値はMyViewHolderインスタンスである。
            return MyViewHolder(rowView)    
        }

onBindViewHolderメソッドを記述する。当該メソッドはRecyclerView.Adapterクラスを継承した中でオーバーライドを行う。

...MainActivity.kt

        // onBindViewHolderの引数は生成したMyViewHolderインスタンスである。
        // 第2引数はリスト型データの番号である。
        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
 
            // row_titeはMyViewHolderの属性値である。
            // myDataset[position].titleは、myDatabaseの要素がRowModelであり、そのRowModelの属性値にtitleである。
            holder.row_title.text = myDataset[position].title
            // row_detailはMyViewHolderの属性値である。
            holder.row_detail.text = myDataset[position].detail
        }

getItemCountメソッドをオーバーライドする。当該メソッドはRecyclerView.Adapterクラスを継承した中でオーバーライドを行う。

override fun getItemCount() = myDataset.size

次にmuDatasetの要素そのもの(Model)について定義を行う。 この定義が終わったら、このModelを利用してリスト型データを生成する関数を生成する関数を定義する。

    class RowModel{
        var title : String  = ""
        var detail : String = ""
private fun createDataset():List<RowModel>{
    val dataList = mutableListOf<RowModel>()
    for (i in 0..49){
        var data = RowModel().apply{
            var it.title  = "タイトル番号"+i
            var it.detail = "詳細の番号" +i
        }
        dataList.add(data)
    }
    return dataList
}

EditTextの入力を受け付けなくさせる方法

EditTextの入力機能を停止させるには、isEnabledを使えば良い。

editText_obj.isEnabled = false

https://developer.android.com/reference/kotlin/android/widget/EditText

isEnabledアトリビュートは、親クラスのViewから継承されたアトリビュートである。

https://developer.android.com/reference/kotlin/android/view/View

関連記事

EditTextまとめ - diadia

Kotlin Radioボタンを実装する方法

Radioボタンを使う際のイメージ

RadioGroupを使う。 リスナーについては2つ考え方がある。 ラジオボタンに実装するのではなく、RadioGroupにリスナーを実装する方法と、通常のボタンにリスナーを実装する方法である。 RadioGroupにリスナーを実装する方法はラジオボタンの切り替えイベントに対する処理を規定するものらしい(おそらく)。 一方通常のボタンにリスナーを設置する方法は、現在選択されているラジオボタンのデータ送信に使われる。

RadioButtonのデフォルト値の定め方

コンストレイントレイアウトでラジオボタンを実装する際には、Checkという項目にチェックだけすれば良い。

RadioButtonにリスナー機能を実装する

まずリスナーをRadioGroupに設置する。リスナー名は、setOnCheckedChangeListenerである。

https://developer.android.com/reference/kotlin/android/widget/RadioGroup.OnCheckedChangeListener

https://developer.android.com/reference/kotlin/android/widget/RadioGroup.OnCheckedChangeListener#oncheckedchanged

onCheckedChanged(group: RadioGroup!, checkedId: Int)

ラジオボタンが押されたもののリソースIDが引数となる。このリソースIDというのがcheckedIdに当たる。したがってwhen構文等を使って一致するリソースIDを特定して処理したいロジックを記述する。このリスナーを使うとラジオボタンが変更されるたびに処理したい内容が実行される。

通常のボタンにリスナーを追加する

RadioGroup.checkedButtonIdを使うと現在選択されているラジオボタンのリソースIDを取得する事ができる。 https://developer.android.com/reference/kotlin/android/widget/RadioGroup#getcheckedradiobuttonid これを使えば、ボタンを押した時にどのボタンが選択されているか把握する事ができる。

エラー:Class "***" must contain at least 1 persistable field. Realm

Realmで以下のエラーが発生した。

Class "***" must contain at least 1 persistable field.

エラー原因コード

import io.realm.RealmObject

//モデルクラスの作成
class WordDB:RealmObject() {
    //フィールドの設定
    var strQuestion:String = "";
    var strAnswer:String = "";
}

修正コード

import io.realm.RealmObject

//モデルクラスの作成
open class WordDB:RealmObject() {
    //フィールドの設定
    var strQuestion:String = "";
    var strAnswer:String = "";
}

openをつけると解消できた。