android java转kotlin + jetpack爬坑日记---持续更新

前言:刚不久前换了份工作,该公司已有的产品即是使用kotlin (少部分代码是java,比如一些第三方库) +jetpack 的方式来写的,所以赶鸭子上架,需要迅速的熟悉代码并运用

上一家公司的项目,因为年头有些久了,使用的java语言编写的,也就一直没有采用kotlin,也没有使用jetpack,所以本文适合已经有java经验开发android app,现在希望转向kotlin + jetpack的读者

我会将在项目实际过程中遇到的各种问题记录于此,方便大家查阅

在这之前,请先查看该网址(GitHub - MindorksOpenSource/from-java-to-kotlin: From Java To Kotlin - Your Cheat Sheet For Java To Kotlin),了解一些基本的java写法转向kotlin的区别,下面的文章中,如果是该网址有的一些写法,则不再列入

该文章持续更新,凡是遇到从java转换到kotlin的问题,就会记录下来,欢迎关注

1.构造函数

这是大家非常熟悉的自定义控件的构造函数,其他类的构造函数也类似

public class MyViewPager extends ViewPager {

    public MyViewPager(@NonNull Context context) {
        super(context);
    }

    public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

}

kotlin的写法如下:

class MyViewPager : ViewPager { //kotlin中,继承用:
    //java写法的构造函数,public MyViewPager()在kotlin中,用constructor关键字代替
    /* 这里我写的时候不是很理解,自己写成了这样
     * constructor(context : Context){ 
     *    super(context)
     *  }
     * 还是习惯了java的写法,用{}来框住方法体,然后在里面写逻辑,但是kotlin这里就直接用:就可以了
     * 写法就是这样,大家慢慢适应就好了
     */
    constructor(context: Context) : super(context) {}

    //这里是不是很好奇,为什么AttributeSet后面有个? 在kotlin中,参数后面带个?代表这个参数如果为空的话,并不会抛出空指针异常,同样对应的还有 !!,双感叹号代表这个参数不能为空,如果为空的话,会抛出空指针异常
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}
}

2.回调函数

写了个测试的类,里面定义了一个接口LayoutChangeListener

public class TestInterface {

    private LayoutChangeListener listener;

    interface LayoutChangeListener{
        void MoveUp();
        void MoveDown();
    }

    public void setListener(LayoutChangeListener listener){
        this.listener = listener;
    }
}

java代码调用如下:

TestInterface testInterface = new TestInterface();
        testInterface.setListener(new TestInterface.LayoutChangeListener() {
            @Override
            public void MoveUp() {

            }

            @Override
            public void MoveDown() {

            }
        });

kotlin则变成这样:

val testInterface = TestInterface() //这里可以用var,也可以val,val相当于java的 final关键字
  //object : 简单粗暴的理解为就是java 的 new xxx       
 testInterface.setListener(object : LayoutChangeListener {
            override fun MoveUp() {}
            override fun MoveDown() {}
        })

3.LiveEventBus

大家以前都是用EventBus,但它可能造成内存泄漏,现在jetpack推出之后,使用LiveEventBus来替代EventBus,它能感知生命周期,使用观察者模式,具体的原理大家自行百度

先简单说明一下LiveEventBus的用法以及和EventBus的区别

//以下是eventbus的使用方法
EventBus.getDefault().register(this)

@Subscribe(threadMode = ThreadMode.MAIN)
    public void onColorEvent(String content){
        
    }

EventBus.getDefault().post("test")

//然后大家会在ondestroy方法里面取消注册
public void onDestroy() {
        super.onDestroy()
        EventBus.getDefault().unregister(this)
    }

//使用LiveEventBus的话,就不需要有注册和取消注册这一步了,只需要在application里面初始化的时候,设置自动回收就可以了
LiveEventBus.
        config().
        supportBroadcast(this). //是否支持使用广播发送数据,这个就可以跨进程通信啦
        lifecycleObserverAlwaysActive(true). //设置为true,则整个生命周期都能收到消息,为false,就只有激活状态才能收到,默认是true
        autoClear(true); //在没有Observer关联的时候是否自动清除LiveEvent以释放内存

//发送方法
LiveEventBus.get("colorEvent").post("test")

//接收方法
LiveEventBus.get("colorEvent").observe(this, new Observer<Object>() {
            @Override
            public void onChanged(Object o) {
                
            }
        });

那么换成kotlin是这样用的

//发送数据
LiveEventBus.get("colorEvent").post("test")

//接收数据
LiveEventBus.get("colorEvent").observe(this, Observer {
            //是不是有点没看懂?这样怎么接收数据呢,
            //别急,这里你会看到有个it参数,它就是值,而且是泛型的
            //比如我们这里发送的是一个string字符串,那么接收就可以这样写
            val test:String = it.toString()

            //而如果我们post的不是字符串,是一个实体类怎么办呢?
            //假设我们发送的是这样:
            //LiveEventBus.get("colorEvent").post(ColorEvent("#ffffff"))
            //这里ColorEvent的实体类我就不贴代码了,自行脑补
            //那么接收代码就是这样:
            val colorEvent:ColorEvent = it as ColorEvent
            //如果我们一个类里面有多个接收方法,那么就这样接收:
            if(it is ClassA){ //这里的 it is ClassA 就是java代码的 it instanceof ClassA
            
            }else if(it is ClassB){
            
            }
        })

4.Intent跳转Activity

java写法:

Intent intent = new Intent(this,ClassA.class);

startActivity(intnet)

kotlin写法:

var intent = Intent(this,ClassA::class.java)
                startActivity(intent)

5. Handler

java写法:

private Handler mHandler =new Handler(){
        @Override
        public void handleMessage(Message msg) {
            mHandler.sendEmptyMessageDelayed(0,1000);
        }
    };

kotlin写法:

private val mHandler = object :Handler(){
        override fun handleMessage(msg: Message?) {
//这里特别留意,因为java的写法是要用mHandler.出来的
//但如果这里前面写个mHandler.那么会编译不通过
            sendEmptyMessageDelayed(0,1000)
        }
    }

6.jetpack---viewbinding

这绝对是我刚接触jetpack以来认为第一个吊炸天的东西,可以省略非常多的代码。基本再也不用findviewById,也不用BindView(butterknife的用法)了,试想一下:一个复杂的界面,几十个id需要处理,光写findview就花了几分钟,还占用大量篇幅

//在模块的build.gradle文件下加入这行代码即可
//但用这玩意,就意味着你要用kotlin来写代码,是的,google就是这么骚,为了让
//用户转变为用kotlin来写代码,这玩意就只支持kotlin
apply plugin: 'kotlin-android-extensions'
<?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">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:id="@+id/tv_hello"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
//这行代码就是把view都引入啦,只要xml布局有id的,这里都有
import kotlinx.android.synthetic.main.activity_main.* 

class KtMainActivity : AppCompatActivity(){

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        tv_hello.text = "aaaa" //这里直接使用id的名称就可以了
    }
}

7. 类引用

java写法

startActivity(new Intent(this,SecondActivity.class))

kotlin写法

startActivity(Intent(this@HelloActivity,SecondActivity::class.java))

8.ExpandListView的convertView引用

java写法

@Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
        if(convertView == null){
            convertView = LayoutInflater.from(context).inflate(R.layout.xxx,parent,false)
        }
        return convertView;
    }

kotlin写法

override fun getGroupView(groupPosition: Int,isExpanded: Boolean,convertView: View?,parent: ViewGroup?): View {
        var convertView = convertView //这里必须重新赋值,直接用convertView的话,下面的convertView = xxx 就报错了,无法编译
       if(convertView == null) {
           convertView = LayoutInflater.from(mContext).inflate(R.layout.adapter_bfb_record_group,null)
       }

        return convertView!!
    }

9. jetpack -- 数据绑定(databinding)

个人认为,数据绑定这玩意的好处,是减少在activity中对于UI数据的操作,当activity比较复杂,上千行代码的时候,用databinding有助于代码简洁

//它是jetpack的组件之一,需要在对应的运行模块的build.gradle文件中加入如下代码
android{
    dataBinding {
        enabled = true
    }
}
//第二步,先写model层(个人习惯,你要先写xml布局也可以)
//接口返回什么,就写什么咯
data class User(val firstname:String)
<!-- 第三步,写xml布局 -->
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >
    <data>
        <!-- name自行定义,你想叫什么都行 -->
        <!-- type 就是对应的model层的类名-->
        <variable name="killaxiao" type="com.example.myapplication.User"/>
    </data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- @{killaxiao.firstname , default=helloWorld} 很好理解吧 -->
    <!-- @{}的语法,上面定义的name.model层的参数,default是默认值 -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{killaxiao.firstname , default=helloWorld}"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:id="@+id/tv_hello"

        />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //使用databinding之后,就不用使用setcontentview了
        //这里ActivityMainBinding 是因为我的布局文件叫activity_main,
        //如果你的布局文件叫activity_test,那么这里就是ActivityTestBinding
        val binding: ActivityMainBinding = DataBindingUtil.setContentView(
                this, R.layout.activity_main)
        //这里在实际使用中,就是从接口获取到值之后,设置进去即可
        binding.killaxiao = User("bbbbb")
    }

10.room数据库

这个数据库让我遇到的坑有点多,先说明基础用法

apply plugin: 'kotlin-kapt' //在运行模块的build.gradle目录加入代码
dependencies{
    implementation "androidx.room:room-runtime:2.3.0"
    kapt "androidx.room:room-compiler:2.3.0" //我是用kotlin开发,所以前缀是kapt,如果是java的,请用annotationProcessor,但本文是介绍kotlin的,所以不做java的讲解
}

引入写完了之后,第一步,先来创建一个表

@Entity(tableName = "users") //这里如果不写tableName的话,那么表名就是类名(UserTable)
data class UserTable(
    //下面这些应该不用过多解释了,userid主键自增,其他的name = "xxx"就是列名
    @PrimaryKey(autoGenerate = true) var userid:Int,
    @ColumnInfo(name = "username") var username:String,
    @ColumnInfo(name = "password") var password:String,
    @ColumnInfo(name = "permission") var permission:String,
    @ColumnInfo(name = "phone") var phone:String,
    @ColumnInfo(name = "company") var company:String
)

然后写它的增删改查方法

@Dao
interface UserDao { //必须是接口

    @Query("select * from users where userid = :id") //方法中的参数,用:id来获取
    fun getUserById(id:Int):UserBean

    @Query("select * from users where username = :name")
    fun getUserByName(name:Int):UserBean

    @Query("select count(*) from users where username = :username and password = :password")
    fun Login(username:String,password:String):Int

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertUser(user:UserTable)

    @Update
    fun updateUser(user:UserTable)

    @Query("update users set username = :username and password = :password and phone = :phone and company = :company where userid = :id")
    fun updateUserById(id:Int,username:String,password:String,phone:String,company:String)
}

接下来创建数据库

//entities里面,有几个表,就写几个创建表对应的类名,version版本号不用说,最后一个参数exportSchema可以不填,默认是true
@Database(entities = [UserTable::class,MessageTable::class],version = 1,exportSchema = true)
abstract class AppDatabase :RoomDatabase(){
    abstract fun userDao():UserDao
    abstract fun messageDao():MessageDao

    companion object{
        private var instance:AppDatabase? = null
        fun getInstance(context: Context):AppDatabase{
            if(instance == null){
                //allowMainThreadQueries是允许在主线程执行
                instance = Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"xingtong")
                    .allowMainThreadQueries().build()
            }
            return instance as AppDatabase
        }

    }


}
//启动页copy了以前项目的,所以是java写的,大家可以自己写kotlin的代码
AppDatabase database = AppDatabase.Companion.getInstance(WelcomeActivity.this);
        try {
            UserDao userDao = AppDatabase.Companion.getInstance(WelcomeActivity.this).userDao();
        //这里是自己写的工具类,读取assets目录下的default_user.json,代码就不贴了
            JSONObject jsonObject = new JSONObject(AssetsReader.getJson("default_user.json", WelcomeActivity.this));
            JSONArray jsonArray = jsonObject.optJSONArray("data");
            for(int i=0;i<jsonArray.length();i++){
                JSONObject obj = jsonArray.optJSONObject(i);
                UserTable userTable = new UserTable(-1,obj.optString("username"),
                        obj.optString("password"),obj.optString("permission"),
                        obj.optString("phone"),obj.optString("company"));
                userDao.insertUser(userTable);
            }
            startActivity(new Intent(WelcomeActivity.this, LoginActivity.class));
            finish();
        }catch (Exception e){
            e.printStackTrace();
        }

NOTE:接下来说坑

1.编译时报错

Schema export directory is not provided to the annotation processor so we cannot export the schema. You can either provide `room.schemaLocation` annotation processor argument OR set exportSchema to false.

这个要在刚才的运行模块build.gradle里面加入代码,或者创建数据库的代码那里 设置 exportSchema =false,不过推荐下面这种做法

defaultConfig {
        applicationId "xxx"
        minSdkVersion 23
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        //加入以下代码
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation":
                                     "$projectDir/schemas".toString()]
            }
        }
    }

2. 接收不到回调

AppDatabase.Companion.getInstance(WelcomeActivity.this,new RoomDatabase.Callback(){
            @Override
            public void onCreate(@NonNull SupportSQLiteDatabase db) {
                try {
                    UserDao userDao = AppDatabase.Companion.getInstance(WelcomeActivity.this).userDao();
                    JSONObject jsonObject = new JSONObject(AssetsReader.getJson("default_user.json", WelcomeActivity.this));
                    JSONArray jsonArray = jsonObject.optJSONArray("data");
                    for(int i=0;i<jsonArray.length();i++){
                        JSONObject obj = jsonArray.optJSONObject(i);
                        UserTable userTable = new UserTable(-1,obj.optString("username"),
                                obj.optString("password"),obj.optString("permission"),
                                obj.optString("phone"),obj.optString("company"));
                        userDao.insertUser(userTable);
                    }
                    startActivity(new Intent(WelcomeActivity.this, LoginActivity.class));
                    finish();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });

在创建数据库的时候,获取回调,是没有响应的,直到有对数据库的具体操作,才会有回调,所以这个回调有点鸡肋,大可不用

3. 上述代码引发的崩溃

java.lang.IllegalStateException: getDatabase called recursively

这是因为AppDatabase.Companion.getInstance得到了一个数据库的对象引用,然后我在getInstance这个方法里面写了

instance!!.beginTransaction() 这行代码,即可收到onCreate回调,然后在回调这里再获取引用,在userDao.insertUser的时候报错了,在获取getInstance的时候,数据库引用已经锁了

所以我去掉了getInstance方法里面的instanse!!.beginTransaction(),改成如下代码,即可正常运行

        AppDatabase database = AppDatabase.Companion.getInstance(WelcomeActivity.this);
        try {
            UserDao userDao = database.userDao();
            JSONObject jsonObject = new JSONObject(AssetsReader.getJson("default_user.json", WelcomeActivity.this));
            JSONArray jsonArray = jsonObject.optJSONArray("data");
            for(int i=0;i<jsonArray.length();i++){
                JSONObject obj = jsonArray.optJSONObject(i);
                UserTable userTable = new UserTable(-1,obj.optString("username"),
                        obj.optString("password"),obj.optString("permission"),
                        obj.optString("phone"),obj.optString("company"));
                userDao.insertUser(userTable);
            }
            startActivity(new Intent(WelcomeActivity.this, LoginActivity.class));
            finish();
        }catch (Exception e){
            e.printStackTrace();
        }

11.kotlin协程

协程的概念在kotlin1.3的版本提出来,所以如果要使用协程的话,依赖的版本要高于1.3才行。至于协程是啥?笔者担心解释不清楚,建议大家还是去看官方的描述,可以简单的认为,协程,就是可以让你用同步化的逻辑去执行异步的代码,可以替代一些回调函数。

笔者在写文章的时候,官方的协程版本是1.3.9

//在项目执行模块的build.gradle配置文件中加入kotlin协程
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"

启动协程的几种方式:

1.runBlocking{},这种方式启动的协程会阻塞当前线程,直至runBlocking里面的逻辑执行结束,实际使用中,应避免使用该方式启动协程,常用于测试协程代码用

        var a = 0
        runBlocking {//创建一个协程,但这个协程是会阻塞当前线程的,直至协程的逻辑运行完毕
                a += 1
        }
        Log.e("test","a的值:$a") //因为runBlocking会阻塞线程,所以这里a输出的值是1

如果改成这样,那么程序就会卡死

        var a = 0
        runBlocking {//创建一个协程,但这个协程是会阻塞当前线程的,直至协程的逻辑运行完毕
            while (true) {
                a += 1
                Log.e("test","a的值:$a") 
            }
        }

2.GlobalScope.launch{},这种方式启动的协程不会阻塞当前线程,但它的生命周期是等同于当前应用的生命周期,即假设这个协程在Test1Activity中启动,然后Test1Activity跳转至Test2Activity,并finish掉了Test1Activity,但协程内的代码依然还是在执行的,实际使用中,可以替代部分后台service执行的代码逻辑,而其它的情况则应该要避免使用这种方式启动协程

        GlobalScope.launch {//这里也是创建一个协程,但这个协程的生命周期是整个应用程序的生命周期,也就是说,即使这个activity销毁了,协程也还在执行
            while (true){ //因为这种启动方式不会阻塞线程,所以即使一直执行,也不会卡死界面
                test_result+=1
                Log.e("test","test_result的值:$test_result")
            }
        }

        btn_click.setOnClickListener {
                //点击之后跳转到Test2Activity,并finish掉当前activity,但因为上方协程生命周期是跟随应用程序的生命周期,不会在activity关闭后停止运行,所以即使到了Test2Activity,也还是一直在输入test_result的值
                startActivity(Intent(this,Test2Activity::class.java))
                finish()
        }

3.CoroutineScope + launch{}/async{},这种方式启动的协程不会阻塞线程,并且会跟随当前生命周期的结束而结束,所以实际使用中,应尽量使用这种方式启动协程

3.1 在activity中实现协程

class MainActivity : BaseActivity(),CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main //会阻塞主线程,官方描述是这种调度器可在主线程执行协程,只能用于界面交互和执行快速工作
        //get() = Dispatchers.IO //不会阻塞主线程,官方描述是用于读写文件,访问网络用的
        //get() = Dispatchers.Default //不会阻塞主线程,最大并发数是CPU的核心数,默认2,官方描述是用于占用大量CPU资源的工作,比如对列表排序或者解析json

private lateinit var test1_job:Job
    private lateinit var test2_job:Job

    override fun initView() {
        test1_job=launch {
                while (true) {
                    if(test1_job.isActive) { //如果不判断协程是否在取消状态,那么即使调用test1_job.cancel,协程也不会停止运行
                        test_result += 1
                        Log.e("test", "test_result的值:$test_result")
                    }
                }
        }

        test2_job=launch {
            var async1 = async {
                delay(3000) //这里特意延迟3秒,再返回5 所以这里设想一下,是网络请求
                5
            }
            var async2 = async {
                delay(5000) //这里延迟5秒,再返回10 ,如果需要同步上面网络请求的结果一起做UI更新的话,async就派上了用场
                10
            }
            //为了展示async的用法,所以上面创建了两个async的协程,分别延迟3秒和5秒,但因为async会同步执行,await()会等待执行结果,所以这里是会延迟5秒之后,得到
            //async1和async2的值,才输出日志
            Log.e("test","async1+async2=${async1.await()+async2.await()}") //输出的结果是15
        }
    }

override fun onDestroy() {
        super.onDestroy()
        test1_job.cancel() //不写取消方法,不会跟随activity的销毁而销毁
        test2_job.cancel()
    }
}

3.2 直接创建一个协程作用域

var scope =CoroutineScope(Dispatchers.IO)
        var test3_job = scope.launch {
            while (true) {
               test_result += 1
               Log.e("test", "test_result的值:$test_result")
            }
        }

12.创建方法

其实这个很简单,但为什么要写呢,是因为我在用了kotlin几个月之后,才留意到原来也可以这么写

val sumLanda:(Int,Int)->Int = {x,y ->x+y} //这个是有返回参数的,和下方写法完全一样

fun sumLanda2(x:Int,y:Int):Int{
        return x+y
    }
//但编辑器上能看得出来两种写法调用的区别,调用sumLanda是和参数调用一样的,是紫色的,但是调用sumLanda2是白色的
Log.e("test",sumLanda(2,6).toString())
Log.e("test",sumLanda2(2,6).toString())

//如果不需要返回参数,就直接这么写就OK了
val sumLanda3:(x:Int,y:Int)->Unit = {it1,it2-> //有多个参数,这里必须声明,因为这种属于是lambda表达式
        Log.e("test",(it1+it2).toString())
    }
    
    val sumLanda4:(x:Int)->Unit ={ //只有一个参数,就可以不声明,默认是it
        Log.e("test",it.toString())
    }

13.类型检测

咱们写java的朋友都知道,有些时候我们写了一些父类,用的T类型,然后到具体的子类使用的时候需要进行类型检测,于是使用 instanceof 关键字,那么在kotlin里面使用的是 is 关键字,不过kotlin还提供了 !is 方法,就是除开某某类型的意思,看代码

fun isTest(obj:Any):Int?{
        if(obj !is String){
            return obj.toString().length
        }
        return null
    }

Log.e("test","isTest:"+isTest(3)) //输出isTest:1
Log.e("test","isTest:"+isTest("12345")) //输出isTest:null

fun isTest2(obj:Any):Int?{
        if(obj is String){
            return obj.toString().length
        }
        return null
    }

Log.e("test","isTest2:"+isTest2(3)) //输出isTest2:null
Log.e("test","isTest2:"+isTest2("12345")) //输出isTest2:5

好的,下面是 Kotlin+Jetpack 实现登录接口请求的示例代码: 首先,在项目的 build.gradle 文件中添加以下依赖项: ```groovy dependencies { // Jetpack 相关依赖 implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1" implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1" // Retrofit 相关依赖 implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:converter-gson:2.9.0" } ``` 然后,创建一个 ViewModel 类,用于处理登录请求和响应: ```kotlin class LoginViewModel : ViewModel() { // 定义 LiveData 对象,用于保存登录结果 private val _loginResult = MutableLiveData<LoginResult>() val loginResult: LiveData<LoginResult> = _loginResult // 定义 Retrofit 对象,用于发起网络请求 private val retrofit = Retrofit.Builder() .baseUrl("https://example.com/") .addConverterFactory(GsonConverterFactory.create()) .build() // 定义登录接口 private val loginApi = retrofit.create(LoginApi::class.java) // 定义登录方法,接收用户名和密码作为参数 fun login(username: String, password: String) { viewModelScope.launch { try { // 发起登录请求 val response = loginApi.login(username, password) // 根据响应状态设置登录结果 if (response.isSuccessful) { _loginResult.value = LoginResult.Success } else { _loginResult.value = LoginResult.Failure(response.message()) } } catch (e: Exception) { _loginResult.value = LoginResult.Error(e) } } } } // 定义登录结果的 sealed class sealed class LoginResult { object Success : LoginResult() data class Failure(val message: String) : LoginResult() data class Error(val exception: Exception) : LoginResult() } ``` 其中,`LoginApi` 是一个 Retrofit 接口,用于定义登录接口: ```kotlin interface LoginApi { @FormUrlEncoded @POST("login") suspend fun login( @Field("username") username: String, @Field("password") password: String ): Response<Unit> } ``` 最后,在 Activity 或 Fragment 中使用 `LoginViewModel` 发起登录请求: ```kotlin class LoginActivity : AppCompatActivity() { private val viewModel by viewModels<LoginViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) // 监听登录结果 viewModel.loginResult.observe(this, { result -> when (result) { is LoginResult.Success -> { // 登录成功,跳到主界面 startActivity(Intent(this, MainActivity::class.java)) finish() } is LoginResult.Failure -> { // 登录失败,弹出提示框 Toast.makeText(this, result.message, Toast.LENGTH_SHORT).show() } is LoginResult.Error -> { // 登录出错,打印日志 Log.e("LoginActivity", "Login error", result.exception) } } }) // 点击登录按钮时发起登录请求 loginButton.setOnClickListener { val username = usernameEditText.text.toString() val password = passwordEditText.text.toString() viewModel.login(username, password) } } } ``` 这样,我们就完成了 Kotlin+Jetpack 实现登录接口请求的示例代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值