Kotlin programming to develop Android applications (Volley+Gson dependent library)

At Google IO 2017, Kotlin was announced as an official language. It is always a good idea to follow the party, and start the Kotlin journey with passion.

After an afternoon exploring Kotlin programming. With trepidation, I researched it.


In fact, whether Kotlin should be learned or not has caused great justice for developers, but in my opinion, it is better to learn more, even if we don't use it often.


1. AndroidStudio supports Kotlin configuration :

If you use Android Studio 3.0 or lower, the Kotlin language is not supported by default, and you need to configure it yourself.

Androistudio 3.0 and above comes with support for Kotlin.

For specific configuration and use, you can refer to the article explained in the previous article, AndroidStudio for Kotlin programming (including 3.0 and 2.x versions) configuration and use .

2. Add the dependency library in Gralde :

Note : The projects shown here already support Kotlin writing, and Kotlin configurations are already available in Gralde of Project and Gralde of Moudle.

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.android.support:recyclerview-v7:25.3.1'
    testCompile 'junit:junit:4.12'

    compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"

    //官方框架
    compile 'com.android.volley:volley:1.0.0'
    compile 'com.google.code.gson:gson:2.8.0'
}

3. Start writing in Kotlin as needed :

How to use AndroidStudio to create Kotlin files or projects, please read AndroidStudio usage of Kotlin programming .

Project requirements :

  • Search director's movies in Doupea API
  • Load movies in the list

Implement :

  • Written in Kotlin
  • Volley+Gson official framework asynchronous loading and parsing
  • Display movies in RecyclerView

Based on the analysis, start writing the code :

1. Write Volley's singleton operation class :

  • objectkeyword declaration singleton class

  • lateinitThe keyword declaration declares a non-null variable, the benefit does not require setting an initial value.

  • lazy()Deferred properties, synchronously produce a single object.

  • ValThe keyword declares a read-only object, and the Varkeyword declares a writable object.

  • Note : Creating an object does not require the use of keywords in Java .new

/***
 *  object 用于单例模式
 *
 *  object声明对象名后,通过对象名来访问,但是不能使用 = 右边赋值。
 *
 */
object  VolleySingletion{
    /**
     * lateinit 声明一个非空变量,且不需要设置初始值。
     */
    private lateinit  var context:Context

    /**
     *  这里使用 延迟属性(lazy properties):首次访问时计算结果,以后再次访问时候,将拷贝第一次记录的结果。
     *
     *
     *   使用形式: var p: String by lazy {   }
     *
     *   lazy()返回一个lazy<T> 的 T 对象.
     *
     *   注意点: lazy属性的计算结果的过程是同步锁的(synchronized)。
     *
     *   作用: 单例对象
     */
    val  requestQueque :RequestQueue by lazy {
        Volley.newRequestQueue(context)
    }

    val  imageLoader :ImageLoader by lazy {
        // 不需要调用  new  关键字才创建对象
        ImageLoader(requestQueque,LruBtimapCache() )
    }

    fun  initConfi(context:Context){
        this.context =context.applicationContext
    }

}

2. Write the LruCache that ImageLoader needs to use :

  • The way a class inherits the parent class and implements the interface:class 类名 :超类名(),接口名

  • In the main constructor of LruBitmapCache, the specified parameter type is Int, and a default value is also specified.

  • LruBitmapCache comes with a primary constructor, so the superclass (here LruCache) must be initialized in the primary constructor.

  • CompanionA keyword that modifies the companion object. Companion object class name, can be omitted
/***
 *   LruBitmapCache主构造函数中,指定一个默认值。
 *
 *   LruBitmapCache带有主构造函数,因此超类(这里是LruCache)必须在主构造函数中初始化。
 *
 */
class LruBtimapCache (size: Int= defaultSize ): LruCache<String,Bitmap>(size) ,ImageLoader.ImageCache{

    override fun getBitmap(url: String): Bitmap ?{
        return get(url)
    }
    override fun putBitmap(url: String?, bitmap: Bitmap?) {
        put(url,bitmap)
    }
    override fun sizeOf(key: String, value: Bitmap): Int {
        return   value.rowBytes*value.height/1024
    }
    /**
     * 使用Companion关键字,伴生对象类名,可以省略。
     */
    companion object{
        /**
         *  val声明一个只读的变量
         *
         */
         val  defaultSize: Int get() {
             val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
             val cacheSize = maxMemory / 8
             return cacheSize
         }
    }
}

3. Write a custom Application :

  • overrideKeywords are used to override methods that exist in superclasses or interfaces
class BaseApplication :Application(){
    override fun onCreate() {
        super.onCreate()
        //初始化Volley配置
        VolleySingletion.initConfi(this)
    }
}

Configure in AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xingen.kotlindemo">
   //联网权限
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:name=".BaseApplication"  //使用自定义的Application
        android:theme="@style/AppTheme">
        <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>

4. Write the entity class

There is also a point of doubt here: datathe keyword decorates the data object, but it must have a primary constructor function.
Therefore, the data object is not decorated with data here.

  • Type followed by ?yes allows the object to be null
//https://api.douban.com/v2/movie/search?q=张艺谋,返回的数据结构。
class MovieList {

    lateinit var subjects: List<Movie>

    class Movie {
       lateinit var year: String
        var title: String? = null
        var id: String? = null
        lateinit  var images: Images

        class Images {
            var small: String?= null

            var large: String? = null
        }
    }
}

5. Write the Adapter of RecyclerView :

  • thisThe reference is the innermost object. If you refer to the outer object, you need to usethis@类名

  • In addition to the way to initialize global variables below, you can also declare the type in the primary constructor

class ImageListAdapter(movieList: List<MovieList.Movie>) : RecyclerView.Adapter<ImageListAdapter.ViewHolder>() {
    /**
     * 指定一个全局的变量,从主构造函数中获取到参数,进行初始化
     */
    var list = movieList

    /**
     * 加载的数量
     */
    override fun getItemCount(): Int {
        return list.size
    }

    /**
     * 创建 ViewHolder
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {

        var rootView = View.inflate(parent.context, R.layout.item_imagelist_layout, null)

        return ViewHolder(rootView)
    }

    /**
     *  绑定ViewHolder,进行加载数据
     */
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.loadImage(position)
    }
    /**
     *  inner修饰内部类
     */
    inner class ViewHolder(var rootView: View) : RecyclerView.ViewHolder(rootView) {

        /**
         * 构建一个加载数据的方法,参数为RecyclerView中的当前的位置
         */
        fun loadImage(position: Int) {
            var iv = rootView.findViewById(R.id.imagelist_iv) as NetworkImageView
            var title = rootView.findViewById(R.id.imagelist_title) as TextView

            /**
             *  this@类名的方式,拿到需要对应类的this指向。
             */
            var  adapter=this@ImageListAdapter
            /**
             *   NetWorkImageView开始加载图片
             */
            iv.setDefaultImageResId(R.mipmap.ic_launcher)
            iv.setErrorImageResId(R.mipmap.ic_launcher)
            iv.setImageUrl(adapter.list[position].images.large,VolleySingletion.imageLoader)

            title.text=adapter.list[position].title
        }
    }
}

6. Write MainActivity to send requests and update data :

/**
 *  一个类继承父类和实现接口的方式; class 类名 :超类名(),接口名
 */
class MainActivity : AppCompatActivity() {
    /**
     * override用于覆写继承父类或者实现接口中方法。
     *
     * fun 用于标识方法
     *
     * 参数形式: 参数名: 类型
     *
     *  ? 是用于指定可以为空对象
     *
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        shwoDiaglog()
        this.initView()
        this.sendRequest()

    }

    lateinit var recyclerView: RecyclerView
    /**
     * 初始化控件
     */
    fun initView() {
        recyclerView = this.findViewById(R.id.main_recycler_view) as RecyclerView
    }

    /**
     *  将网络数据加载到RecyclerView
     */
    fun loadData(movieList: List<MovieList.Movie>) {
        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.adapter = ImageListAdapter(movieList)
    }

   lateinit var progressDialog: ProgressDialog
    /**
     * 显示dialog
     */
    fun shwoDiaglog() {
        progressDialog = ProgressDialog(this)
        progressDialog.show()
    }

    /**
     * 取消dialog
     */
    fun cancleDialog() {
        progressDialog.dismiss()
    }

    /**
     * Toast显示
     */

    fun loadToast(content: String?) {
        Toast.makeText(this, content, Toast.LENGTH_SHORT).show()
    }

    /**
     * 发送请求,这里使用douban公开的搜索电影的API
     */
    fun sendRequest() {
        var url = "https://api.douban.com/v2/movie/search?q=张艺谋"

        val request = StringRequest(url, Response.Listener<String> {
            response -> 

            //请求成功,Gson解析json
            var movilist = Gson().fromJson(response, MovieList::class.java)
            loadData(movilist.subjects)
            cancleDialog()

        }, Response.ErrorListener {
            error -> 

            loadToast(error.message)
            cancleDialog()
        })
        // 单利类中对象的引用
        VolleySingletion.requestQueque.add(request)
    }
}

The code of activity_main.xml is as follows:

<android.support.constraint.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="com.xingen.kotlindemo.MainActivity">

  <android.support.v7.widget.RecyclerView
      android:id="@+id/main_recycler_view"
      android:layout_width="match_parent"
      android:layout_height="match_parent">

  </android.support.v7.widget.RecyclerView>

</android.support.constraint.ConstraintLayout>

item_imagelist_layout.xml code is as follows:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">
    <com.android.volley.toolbox.NetworkImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:scaleType="centerCrop"
        android:id="@+id/imagelist_iv"/>
    <TextView
        android:layout_width="wrap_content"
        android:id="@+id/imagelist_title"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="20dp"
        android:layout_height="wrap_content" />

</LinearLayout>

The final directory structure of the project is as follows:

write picture description here

Project running results :

write picture description here

Project code : https://github.com/13767004362/KotlinVolleyDemo

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325528057&siteId=291194637