【Kotlin】RecyclerView のクリックイベントを親のActivity(呼び出し元)で定義する
Adapterファイル
class CustomAdapter(private val customList: Array<String>) : RecyclerView.Adapter<CustomAdapter.CustomViewHolder>(){ ........ //リスナー格納変数 lateinit var listener: OnItemClickListener // ViewHolderに表示する画像とテキストを挿入 override fun onBindViewHolder(holder: CustomViewHolder, position: Int) { // タップしたとき holder.view.setOnClickListener { listener.onItemClickListener(it, position, customList[position]) } } //インターフェースの作成する interface OnItemClickListener{ //呼び出し元で利用予定の引数を用意しておく fun onItemClickListener(view: View, position: Int, clickedText: String) } // リスナーをセットする fun setOnItemClickListener(listener: OnItemClickListener){ this.listener = listener } }
呼び出し元Activityファイル
adapter.setOnItemClickListener(object : CustomAdapter.OnItemClickListener { override fun onItemClickListener(view: View, position: Int, clickedText: String) { tTap.text = "${clickedText}がタップされました。" } })
【Kotlin】Socket.io で簡易チャットアプリ
ネットワーク周りの設定
AndroidManifest.xml の修正
インターネット通信を許可するために
<uses-permission android:name="android.permission.INTERNET" />
を追加する
http通信を許可するために
android:usesCleartextTraffic="true"
を追加する
以下全文
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.sample.websocket"> <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:usesCleartextTraffic="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".ChatBoxActivity"></activity> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
ライブラリを追加
WebSocketを使用するために socket.io-client を追加
dependencies { ・・・・ implementation('io.socket:socket.io-client:1.0.0') { exclude group: 'org.json', module: 'json' } implementation 'com.google.android.material:material:1.0.0-rc01' implementation 'androidx.recyclerview:recyclerview:1.0.0' }
作成するファイル
ファイル名 | 内容 |
---|---|
MainActivity.kt | 簡易ログイン画面 |
activity_main.xml | 簡易ログイン画面レイアウトファイル |
Messsage.kt | メッセージデータ用クラス |
ChatBoxAdapter.kt | チャット一覧用アダプターファイル |
item.xml | チャット一覧用レコードレイアウトファイル |
ChatBoxActivity.kt | チャット一覧画面 |
activity_chat_box.xml | チャット一覧画面レイアウトファイル |
簡易ログイン画面の作成
ユーザ 名を入力するためだけの簡易的な画面 チャット画面に表示させる名前を入力
MainActivity.kt
package com.example.android.sample.websocket import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { companion object{ const val NICKNAME: String = "usernickname" } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) enterchat.setOnClickListener{ if(nickname.text.isNotEmpty()){ var i: Intent = Intent(this, ChatBoxActivity::class.java) i.putExtra(NICKNAME, nickname.text.toString()) startActivity(i) } } } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <EditText android:id="@+id/nickname" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textSize="30dp" android:hint="Enter your name !"/> <Button android:layout_below="@+id/nickname" android:id="@+id/enterchat" android:text="Go to Chat" android:layout_height="wrap_content" android:layout_width="match_parent"> </Button> </RelativeLayout>
メッセージ用データクラスを作成
Message.kt
package com.example.android.sample.websocket data class Message (var nickname: String, var message:String)
メッセージ一覧(RecylerView)のレコードレイアウトファイル
<LinearLayout android:orientation="horizontal" android:layout_height="wrap_content" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <TextView android:id="@+id/nickname" android:textSize="15dp" android:textStyle="bold" android:text="NickName : " android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView> <TextView android:id="@+id/message" android:text="message " android:layout_width="wrap_content" android:layout_height="wrap_content"> </TextView> </LinearLayout>
メッセージ一覧(RecyclerView)に使用するアダプターを作成
取得したデータをRecylerViewに適用する
ChatBoxAdapter.kt
package com.example.android.sample.websocket import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.item.view.* class ChatBoxAdapter(private val MessageList: ArrayList<Message>) : RecyclerView.Adapter<ChatBoxAdapter.MyViewHolder>() { inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) { val nickName = view.nickname val message = view.message } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatBoxAdapter.MyViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false) return MyViewHolder(view) } override fun getItemCount(): Int { return MessageList.size } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val m: Message = MessageList.get(position) holder.nickName.text = m.nickname holder.message.text = m.message } }
チャット画面の実装
ChatBoxActivity.kt
package com.example.android.sample.websocket import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.util.Log import android.widget.Toast import androidx.recyclerview.widget.LinearLayoutManager import io.socket.client.IO import io.socket.client.Socket import io.socket.emitter.Emitter import kotlinx.android.synthetic.main.activity_chat_box.* import org.json.JSONException import org.json.JSONObject import java.net.URISyntaxException class ChatBoxActivity : AppCompatActivity() { private lateinit var socket: Socket public var Nickname: String = "" public lateinit var MessageList: ArrayList<Message> public lateinit var chatBoxAdapter: ChatBoxAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_chat_box) val intent: Intent = getIntent() Nickname = intent.getStringExtra(MainActivity.NICKNAME) try { socket = IO.socket("http://[チャットサーバのURL]") socket.connect() socket.emit("join", Nickname) } catch (e: URISyntaxException){ e.printStackTrace() } MessageList = arrayListOf() messagelist.adapter = ChatBoxAdapter(MessageList) messagelist.layoutManager = LinearLayoutManager(this) send.setOnClickListener{ socket.emit("messagedetection", Nickname, message.text.toString()) message.setText("") } socket.on("userjoinedthechat", Emitter.Listener {args -> runOnUiThread(Runnable { val data = args[0] as String Toast.makeText(this,data,Toast.LENGTH_SHORT).show(); }) }) socket.on("userdisconnect", Emitter.Listener {args -> runOnUiThread(Runnable { val data = args[0] as String Toast.makeText(this,data, Toast.LENGTH_SHORT).show(); }) }) socket.on("message", Emitter.Listener {args -> runOnUiThread(Runnable { val data = args[0] as JSONObject try{ var nickNameString: String = data.getString("senderNickname") var messageString: String = data.getString("message") val m: Message = Message(nickNameString, messageString) MessageList.add(m) chatBoxAdapter = ChatBoxAdapter(MessageList) chatBoxAdapter.notifyDataSetChanged() messagelist.adapter = chatBoxAdapter } catch (e : JSONException){ e.printStackTrace() } }) }) } override fun onDestroy() { super.onDestroy() socket.disconnect() } }
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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=".ChatBoxActivity"> <LinearLayout android:weightSum="3" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <View android:layout_marginTop="5mm" android:id="@+id/separator" android:layout_width="match_parent" android:layout_height="1dp" android:background="@android:color/darker_gray"/> <androidx.recyclerview.widget.RecyclerView android:id="@+id/messagelist" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="3" android:clipToPadding="false" android:scrollbars="vertical" /> <LinearLayout android:weightSum="3" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:id="@+id/message" android:layout_weight="3" android:layout_width="wrap_content" android:hint="your message" android:layout_height="match_parent" /> <Button android:id="@+id/send" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#00000000" android:text="send" /> </LinearLayout> </LinearLayout> </RelativeLayout>
【Kotlin】Bottom Navigation + Tab Layout
元ネタ
Gradle の修正
ナビゲーションを利用するためにbuild.gradle(:app)ファイルを修正する
dependencies { ・・・・ implementation "com.google.android.material:material:1.1.0-alpha06" def nav_version = '2.0.0' implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" }
ボトムナビゲーションのメニュー用レイアウトファイルを作成
res/menu/navigation.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/navigation_call" android:icon="@android:drawable/ic_menu_call" android:title="Call"/> <item android:id="@+id/navigation_search" android:icon="@android:drawable/ic_menu_search" android:title="Search"/> </menu>
activity_main.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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/container" android:orientation="vertical" tools:context=".MainActivity"> <FrameLayout android:id="@+id/content" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> </FrameLayout> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/navigation" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:background="?android:attr/windowBackground" app:menu="@menu/navigation"/> </LinearLayout>
4つのフラグメントファイルを作成する
CallFragment.kt
ボトムナビゲーションの「Call」に対応するフラグメント
SearchFragment.kt
ボトムナビゲーションの「Search」に対応するフラグメント
VoiceCallFragment.kt
「Call」画面内の「VOICE CALL」タブに対応するフラグメント
VideoCallFragment.kt
「Call」画面内の「VIDEO CALL」タブに対応するフラグメント
それぞれのfragmentに対応するレイアウトファイルも作成しておく
ここでは fragment_voice_call.xml / fragment_video_call.xml はシンプルなレイアウトファイルとする
fragment_voide_call.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".VideoCallFragment"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/hello_blank_fragment" /> </FrameLayout>
fragment_voice_call.xml も同様のレイアウトファイルを作成する
MainActivity.ktの修正
import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.FrameLayout import androidx.fragment.app.Fragment import com.google.android.material.bottomnavigation.BottomNavigationItemView import com.google.android.material.bottomnavigation.BottomNavigationView import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { private var content : FrameLayout? = null private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item -> when(item.itemId){ R.id.navigation_call -> { val fragment = CallFragment() addFragment(fragment) return@OnNavigationItemSelectedListener true } R.id.navigation_search -> { val fragment = SearchFragment() addFragment(fragment) return@OnNavigationItemSelectedListener true } } false } private fun addFragment(fragment: Fragment) { supportFragmentManager .beginTransaction() .setCustomAnimations(R.anim.nav_default_pop_enter_anim, R.anim.nav_default_exit_anim) .replace(R.id.content, fragment, fragment.javaClass.simpleName) .commit() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener) val fragment = CallFragment() addFragment(fragment) } }
この状態でビルドすると以下のような表示になるはず
タブレイアウト(View Pager)用のAdapterを作成する
MyPageAdapter.kt
class MyPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm,BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT){ override fun getItem(position: Int): Fragment { when(position){ 0 -> { return VoiceCallFragment() } else -> { return VideoCallFragment() } } } override fun getCount(): Int { return 2 } override fun getPageTitle(position: Int): CharSequence? { return when(position){ 0 -> "Voice Call2" else -> { "Video Call2" } } } }
fragment_call.xmlファイルの修正
<androidx.constraintlayout.widget.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"> <androidx.viewpager.widget.ViewPager android:id="@+id/viewpager_main" android:layout_width="match_parent" android:layout_height="wrap_content" tools:ignore="MissingConstraints"> <com.google.android.material.tabs.TabLayout android:id="@+id/tabs_main" android:layout_width="match_parent" android:layout_height="wrap_content" app:tabMode="fixed"/> </androidx.viewpager.widget.ViewPager> </androidx.constraintlayout.widget.ConstraintLayout>
CallFragment.ktの修正
package com.example.android.sample.bnavitab import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.viewpager.widget.ViewPager import com.google.android.material.tabs.TabLayout class CallFragment : Fragment() { private lateinit var viewPager: ViewPager private lateinit var tabs: TabLayout override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view: View = inflater.inflate(R.layout.fragment_call, container, false) viewPager = view.findViewById(R.id.viewpager_main) tabs = view.findViewById(R.id.tabs_main) val fragmentAdapter = MyPagerAdapter(childFragmentManager) viewPager.adapter = fragmentAdapter tabs.setupWithViewPager(viewPager) return view } }
この状態でビルドすると以下のような表示になるはず
【Kotlin】Retrofit デバッグログ出力
gradleファイルに記載を追記
dependencies {
・・・
implementation 'com.squareup.okhttp3:logging-interceptor:3.8.1'
}
Retrofitインスタンス生成時に記載を追記
package com.example.android.sample.twicass import com.example.android.sample.twicass.conf.Constant import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory class RetrofitInstance { companion object{ fun getRetrofitInstance(): Retrofit{ //以下追加 val httpLogging = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) val httpClientBuilder = OkHttpClient.Builder().addInterceptor(httpLogging) val retrofit = Retrofit.Builder().baseUrl(Constant.BASE_URL).addConverterFactory( GsonConverterFactory.create() ).client(httpClientBuilder.build()).build() return retrofit } } }
エラー時の処理にLog出力処理を記載する
call.enqueue(object :Callback<SearchLiveMovieResponse>{ override fun onFailure(call: Call<SearchLiveMovieResponse>?, t: Throwable?) { //処理が失敗した時の内容を出力 Log.d("fetchItems", "response fail") Log.d("fetchItems", "throwable :$t") } override fun onResponse( call: Call<SearchLiveMovieResponse>?, response: Response<SearchLiveMovieResponse>? ) { if(response!!.isSuccessful){ list.addAll(response!!.body().movieInfoList) adapter.notifyDataSetChanged() } Log.d("fetchItems", "response code:" + response.code()) Log.d("fetchItems", "response errorBody:" + response.errorBody()) } })
【Kotlin】RecyclerViewで無限スクロールを実装する (APIからデータ取得編)
Koltin でRecyclerViewを使って無限スクールを実装する方法をメモります。
今回はAPIからデータを取得して表示する方法編です。
実際にはこちらの方がよく使うパターンだと思います。
gradleファイルの修正
以下のライブラリを追加します。
dependencies { ・・・ implementation 'com.squareup.retrofit2:retrofit:2.0.2' implementation 'com.squareup.retrofit2:converter-gson:2.0.2' implementation 'androidx.recyclerview:recyclerview:1.1.0' }
レイアウトファイルの追加
activity_main.xmlの修正
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.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"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerview" android:layout_width="match_parent" android:layout_height="match_parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
レコード用レイアウトファイル
row.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:background="@color/colorPrimaryDark" android:layout_height="44dp"> <TextView android:id="@+id/textTitle" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="8dp" android:textStyle="bold" android:textSize="17sp" android:textColor="@android:color/white" android:gravity="center_vertical" android:text="Lorem Ipsum" /> <TextView android:id="@+id/subtitle" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="8dp" android:textStyle="bold" android:textSize="17sp" android:textColor="@android:color/white" android:gravity="center_vertical" android:text="Lorem Ipsum" /> </LinearLayout>
ローディング表示用レコードのレイアウトファイル
データ読み込み中に一覧の一番下にローディング表示したレコードを表示させます。 そのレコードのレイアウトファイルです。
progressbar.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="50dp" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="horizontal"> <ProgressBar android:id="@+id/progressbar" android:layout_width="24dp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" /> </LinearLayout> </LinearLayout>
Adapterファイルの作成
Adapterとは?
Viewとデータの橋渡しをするもの
RecyclerViewAdapter.kt
package com.example.android.sample.retrofitinfinite import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.row.view.* import java.lang.IllegalArgumentException class RecyclerViewAdapter(var list: ArrayList<Data>) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){ companion object{ private const val VIEW_TYPE_DATA = 0 private const val VIEW_TYPE_PROGRESS = 1 } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when(viewType){ VIEW_TYPE_DATA -> { val view = LayoutInflater.from(parent.context).inflate(R.layout.row, parent, false) DataViewHolder(view) } VIEW_TYPE_PROGRESS -> { val view = LayoutInflater.from(parent.context).inflate(R.layout.progressbar, parent, false) ProgressViewHolder(view) } else -> throw IllegalArgumentException("Different View type") } } override fun getItemCount(): Int { return list.size } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if(holder is DataViewHolder) { holder.textTitle.text = list.get(position).title holder.textSubtitle.text = list.get(position).subtitle } } inner class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){ var textTitle = itemView.textTitle var textSubtitle = itemView.subtitle init { itemView.setOnClickListener{ Toast.makeText(itemView.context, list.get(adapterPosition).title, Toast.LENGTH_LONG).show() } } } inner class ProgressViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){ } override fun getItemViewType(position: Int): Int { var viewtype = list.get(position).category return when(viewtype){ "data" -> VIEW_TYPE_DATA else -> VIEW_TYPE_PROGRESS } } }
Dataクラスの作成
データクラスとは?
何もしない、データを保持するためだけのクラス。 Kotlinでは、これは データクラス と呼ばれ、 data としてマークされています。
1レコードごとのデータをここで定義する
Data.kt
package com.example.android.sample.retrofitinfinite class Data(var category: String) { var title: String? = null //タイトル var subtitle: String? = null //サブタイトル init { this.category = category } }
インターフェースの実装
Retrofit では API のリクエスト先のエンドポイントを Interface で定義します。
DataApi.kt
package com.example.android.sample.retrofitinfinite import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Query interface DataApi { @GET("data.php") fun getData(@Query("index") index: Int): Call<List<Data>> }
APIリクエストを生成するクラス
定義したインターフェースからAPIのリクエストを生成するクラスを作成する
RetrofitInstance.kt
package com.example.android.sample.retrofitinfinite import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory class RetrofitInstance { companion object{ fun getRetrofitInstance(): Retrofit{ val retrofit = Retrofit.Builder().baseUrl("https://www.androidride.com/").addConverterFactory( GsonConverterFactory.create() ).build() return retrofit } } }
MainActivity.tk の修正
必要なファイルは揃ったのでMainActivity.ktを修正します。
package com.example.android.sample.retrofitinfinite import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.Adapter import android.widget.Toast import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.activity_main.* import retrofit2.Call import retrofit2.Callback import retrofit2.Response import retrofit2.Retrofit class MainActivity : AppCompatActivity() { lateinit var list: ArrayList<Data> lateinit var adapter: RecyclerViewAdapter var notLoading = true lateinit var layoutManager: LinearLayoutManager lateinit var api: DataApi lateinit var retrofit: Retrofit override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) list = ArrayList() adapter = RecyclerViewAdapter(list) recyclerview.setHasFixedSize(true) layoutManager = LinearLayoutManager(this) recyclerview.layoutManager = layoutManager recyclerview.adapter = adapter retrofit = RetrofitInstance.getRetrofitInstance() api = retrofit.create(DataApi::class.java) load(0) addScrolllistener() } private fun addScrolllistener() { recyclerview.addOnScrollListener(object: RecyclerView.OnScrollListener(){ override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { if(notLoading && layoutManager.findLastCompletelyVisibleItemPosition() == list.size - 1){ list.add(Data("progress")) adapter.notifyItemInserted(list.size - 1) notLoading = false val call: Call<List<Data>> = api.getData(list.size -1) call.enqueue(object : Callback<List<Data>>{ override fun onResponse( call: Call<List<Data>>?, response: Response<List<Data>>? ) { list.removeAt(list.size - 1) adapter.notifyItemRemoved(list.size) if(response!!.body().isNotEmpty()){ list.addAll(response.body()) adapter.notifyDataSetChanged() notLoading = true } else { Toast.makeText(applicationContext, "End of data reached", Toast.LENGTH_LONG).show() } } override fun onFailure(call: Call<List<Data>>?, t: Throwable?) { } }) } } }) } private fun load(i: Int) { val call: Call<List<Data>> = api.getData(0) call.enqueue(object: Callback<List<Data>>{ override fun onFailure(call: Call<List<Data>>?, t: Throwable?) { } override fun onResponse(call: Call<List<Data>>?, response: Response<List<Data>>?) { if(response!!.isSuccessful){ list.addAll(response!!.body()) adapter.notifyDataSetChanged() } } }) } }