Не давно на хабре была статья в которой предлагалось сделать 8 учебных проектов. Мне там приглянулся трекер криптовалют, дабы было хоть как-то интереснее чем просто Get запрос, было решено сделать его на Kotlin. Итак, в этом туториале вы узнаете следующее:
- Как делать Get запросы с Retrofit
- Retrofit и Rx
- RecyclerView с Котлином
- Извлечение данных с api
Введение
Упустим то как включить поддержку Котлина и прочие очевидные и понятные вещи, вроде создания проекта. Мы будем использовать вот это api
Настройка Manifest
Для того чтобы делать запросы нужно иметь разрешение на использование сети:
<uses-permission android:name="android.permission.INTERNET"/>
Добавление библиотек в Gradle
Нам понадобится Retrofit и RxAndroid.
//Retrofit
compile "com.squareup.retrofit2:retrofit:2.3.0"
compile "com.squareup.retrofit2:adapter-rxjava2:2.3.0"
compile "com.squareup.retrofit2:converter-gson:2.3.0"
//RxAndroid
compile "io.reactivex.rxjava2:rxandroid:2.0.1"
Создаем макеты
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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="com.kram.vlad.cryptocurrency.activitys.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>
RecyclerView должен знать как выглядят его элементы для этого нам нужно создать item.xml.
item.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_launcher_background"/>
<TextView
android:id="@+id/symbol"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginStart="11dp"
android:layout_marginTop="11dp"
android:layout_toEndOf="@+id/imageView"
android:text="TextView"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/symbol"
android:layout_marginStart="11dp"
android:layout_toEndOf="@+id/symbol"
android:text="|"
android:textColor="@android:color/black"
android:textSize="18sp"/>
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/textView2"
android:layout_marginStart="12dp"
android:layout_toEndOf="@+id/textView2"
android:text="TextView"
android:textColor="@android:color/black"
android:textSize="14sp"/>
<TextView
android:id="@+id/money"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/name"
android:layout_alignParentEnd="true"
android:layout_marginEnd="13dp"
android:text="TextView"
android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/imageView"
android:layout_marginBottom="10dp"
android:layout_marginLeft="20dp"
android:layout_toEndOf="@+id/imageView"
android:text="24h:"/>
<TextView
android:id="@+id/textView7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textView6"
android:layout_alignBottom="@+id/textView6"
android:layout_alignEnd="@+id/name"
android:text="7d:"/>
<TextView
android:id="@+id/hours"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textView6"
android:layout_alignBottom="@+id/textView6"
android:layout_toEndOf="@+id/textView6"
android:text="-2.94%"
android:textStyle="bold"/>
<TextView
android:id="@+id/days"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/textView7"
android:layout_toEndOf="@+id/textView7"
android:text="+10.19%"
android:textStyle="bold"/>
</RelativeLayout>
</android.support.v7.widget.CardView>
</android.support.constraint.ConstraintLayout>
Делаем модель для парсинга ответа
Для таких целей неплохо подойдет какой-нибудь pojo генератор.
ResponseItrem
data class ResponseItem(@SerializedName("id")
@Expose var id: String?,
@SerializedName("name")
@Expose var name: String?,
@SerializedName("symbol")
@Expose var symbol: String?,
@SerializedName("rank")
@Expose var rank: String?,
@SerializedName("price_usd")
@Expose var priceUsd: String?,
@SerializedName("price_btc")
@Expose var priceBtc: String?,
@SerializedName("24h_volume_usd")
@Expose var _24hVolumeUsd: String?,
@SerializedName("market_cap_usd")
@Expose var marketCapUsd: String?,
@SerializedName("available_supply")
@Expose var availableSupply: String?,
@SerializedName("total_supply")
@Expose var totalSupply: String?,
@SerializedName("max_supply")
@Expose var maxSupply: String?,
@SerializedName("percent_change_1h")
@Expose var percentChange1h: String?,
@SerializedName("percent_change_24h")
@Expose var percentChange24h: String?,
@SerializedName("percent_change_7d")
@Expose var percentChange7d: String?,
@SerializedName("last_updated")
@Expose var lastUpdated: String?) {
Создаем простой Get запрос
Мы будем использовать Rx поэтому наша Get функция должна возвращать Observable. Также прямо здесь мы создаем Factory, с него будем получать объект Retrofit'а.
GetInterface
interface RetrofitGetInterface {
@GET("v1/ticker/")
fun getCryptocurrency(): Observable<List<ResponseItem>>
companion object Factory {
fun create(): RetrofitGetInterface {
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create()) // говорим чем парсим
.baseUrl("https://api.coinmarketcap.com/") // базовая часть ссылки
.build()
return retrofit.create(RetrofitGetInterface::class.java)
}
}
}
Делаем запрос
Для запросов мы будем использовать Rx. Если вы не знакомы с реактивным программированием — вы не знаете что теряете.
Код для запроса
val apiService = RetrofitGetInterface.create()
apiService.getCryptocurrency()
.observeOn(AndroidSchedulers.mainThread())// Говорим в какой поток вернуть
.subscribeOn(Schedulers.io()) // Выбераем io - для работы с данными и интернетом
.subscribe({
result -> arrayListInit(result) // Здесь у нас калбек
}, { error ->
error.printStackTrace()
})
Делаем адаптер для списка
class RecyclerViewAdapter(private val result: List<ResponseItem>,
val resources: Resources):RecyclerView.Adapter<RecyclerViewAdapter.CardViewHolder>()
Данные которые мы получили с нашего запроса нужно засунуть в какой-нибудь массив(List) ResponseItem. Его нужно передать в адаптер, чтобы он наполнял наш список.
В GetItemCount мы должны возвращать размер массива для того чтобы адаптер знал сколько элементов будет в нашем списке.
override fun getItemCount() = result.size //Возвращаем размер масива данных
В OnCreateViewHolder мы должны инфлейтнуть макет нашего итема.
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): CardViewHolder {
return CardViewHolder(LayoutInflater.from(parent?.context)
.inflate(R.layout.item, parent, false)) //Говорим RecyclerView как должен выглядеть item
}
Все время в коде светится какой-то CardViewHolder. Он должен наполнять вьюхи каждого итема данными из массива.
class CardViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
fun bind(result: List<ResponseItem>, position: Int, resources: Resources) {
val responseItem: ResponseItem = result.get(position)
itemView.symbol.text = responseItem.symbol
itemView.name.text = responseItem.name
...
}
}
Функция bind берет все необходимые данные и наполняет ими вьюхи. К этой функции мы обращаемся в onBindViewHolder. И благодаря синтаксису языка делаем это довольно красиво.
override fun onBindViewHolder(holder: CardViewHolder, position: Int)
= holder.bind(result, position, resources)
Последний шаг: прикрепляем наш адаптер
Для этого в калбеке нужно просто прописать:
recyclerView.adapter = RecyclerViewAdapter(result, resources)
recyclerView.layoutManager = LinearLayoutManager(this)
Исходники здесь. Туториал вышел довольно коротким и простым, поэтому объяснений было немного, но если что-то непонятно могу ответить, и все же у нас есть готовый трекер криптовалют.