LeanCloud 快速实现服务端

1. 实现与服务器交互平台

  1.1 Google 平台的 Firebase (需要科学网络)    

Firebasehttps://firebase.google.cn/

  1.2 LeanCloud 平台

LeanCloudhttps://www.leancloud.cn/

2. 配置信息

  2.1 在 LeanCloud 控制台创建应用, 根据 SDK下载 开发指南配置应用

  2.2 配置文件 build.gradle 添加库

   //视图绑定
   buildFeatures {
        viewBinding = true
    }

    //使用存储功能
    implementation 'cn.leancloud:storage-android:8.2.5'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    //使用即时通讯 / 推送服务
    implementation 'cn.leancloud:realtime-android:8.2.5'

  2.3 实现 Application, MyApplication.kt

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        LeanCloud.initialize(this, "bl8p52gwz7pVkOhMg3unRaT4-gzGzoHsz", "dL6QC52vonm41TegX4i5jB7S", "https://bl8p52gw.lc-cn-n1-shared.com")
    }
}

  2.4 配置文件 AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <!-- 基本模块(必须)START -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.LeanDemo"
        tools:targetApi="31">
        <activity
            android:name=".SignUpActivity"
            android:exported="false">
            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
        <activity
            android:name=".LoginActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
        <activity android:name=".MainActivity" />
        <!-- 即时通讯和推送 START -->
        <!-- 即时通讯和推送都需要 PushService -->
        <service android:name="cn.leancloud.push.PushService" />
        <receiver
            android:name="cn.leancloud.push.LCBroadcastReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.USER_PRESENT" />
                <action
                    android:name="android.net.conn.CONNECTIVITY_CHANGE"
                    tools:ignore="BatteryLife" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

  2.5 退出菜单布局 menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/menuLogout"
        android:title="退出" />
</menu>

3. 登录页面实现

  3.1 布局文件 activity_login.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=".LoginActivity">

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextUsername"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="用户名" />
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputLayout2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textInputLayout">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextPassword"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="密码" />
    </com.google.android.material.textfield.TextInputLayout>

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />

    <Button
        android:id="@+id/buttonSignUp"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:text="注册"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textInputLayout2" />

    <Button
        android:id="@+id/buttonLogin"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:enabled="false"
        android:text="登录"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline"
        app:layout_constraintTop_toBottomOf="@+id/textInputLayout2" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:visibility="invisible"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/buttonSignUp" />

</androidx.constraintlayout.widget.ConstraintLayout>

  3.2 实现事件, LoginActivity.kt

class LoginActivity : AppCompatActivity() {
    private lateinit var binding: ActivityLoginBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityLoginBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.editTextUsername.addTextChangedListener(watcher)
        binding.editTextPassword.addTextChangedListener(watcher)
        //注册
        binding.buttonSignUp.setOnClickListener {
            startActivity(Intent(this, SignUpActivity::class.java))
        }
        //登录
        binding.buttonLogin.setOnClickListener {
            login()
        }
    }

    //登录
    private fun login() {
        binding.progressBar.visibility = View.VISIBLE
        val name = binding.editTextUsername.text?.trim().toString()
        val pwd = binding.editTextPassword.text?.trim().toString()
        LCUser.logIn(name, pwd).subscribe(object : Observer<LCUser> {
            override fun onSubscribe(d: Disposable) {}
            //登录成功
            override fun onNext(t: LCUser) {
                binding.progressBar.visibility = View.INVISIBLE
                Toast.makeText(this@LoginActivity, "登录成功", Toast.LENGTH_SHORT).show()
                startActivity(Intent(this@LoginActivity, MainActivity::class.java))
                finish()
            }
            //登录失败
            override fun onError(e: Throwable) {
                Toast.makeText(this@LoginActivity, "${e.message}", Toast.LENGTH_SHORT).show()
                binding.progressBar.visibility = View.INVISIBLE
            }
            override fun onComplete() {}
        })
    }

    //监听 EditText
    private val watcher = object : TextWatcher {
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            val t1 = binding.editTextUsername.text.toString().isNotEmpty()
            val t2 = binding.editTextPassword.text.toString().isNotEmpty()
            binding.buttonLogin.isEnabled = t1 and t2
        }
        override fun afterTextChanged(s: Editable?) {}
    }
}

4. 注册页面实现

  4.1 布局文件 activity_sign_up.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=".SignUpActivity">

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputLayout3"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextUsername"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="用户名" />
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputLayout4"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textInputLayout3">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextPassword"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="密码" />
    </com.google.android.material.textfield.TextInputLayout>

    <Button
        android:id="@+id/buttonSignUpConfirm"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:enabled="false"
        android:text="注册并登录"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textInputLayout4" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:visibility="invisible"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/buttonSignUpConfirm" />
</androidx.constraintlayout.widget.ConstraintLayout>

  4.2 实现事件 SignUpActivity.kt

class SignUpActivity : AppCompatActivity() {
    private lateinit var binding: ActivitySignUpBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivitySignUpBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.editTextUsername.addTextChangedListener(watcher)
        binding.editTextPassword.addTextChangedListener(watcher)
        //注册按钮
        binding.buttonSignUpConfirm.setOnClickListener {
            signUp()
        }
    }

    //注册
    private fun signUp() {
        binding.progressBar.visibility = View.VISIBLE
        val name = binding.editTextUsername.text?.trim().toString()
        val pwd = binding.editTextPassword.text?.trim().toString()
        LCUser().apply {
            username = name
            password = pwd
            signUpInBackground().subscribe(object : Observer<LCUser> {
                override fun onSubscribe(d: Disposable) {}
                //注册成功
                override fun onNext(t: LCUser) {
                    Toast.makeText(this@SignUpActivity, "注册成功", Toast.LENGTH_SHORT).show()
                    LCUser.logIn(name, pwd).subscribe(object : Observer<LCUser> {
                        override fun onSubscribe(d: Disposable) {}
                        override fun onNext(t: LCUser) {
                            binding.progressBar.visibility = View.INVISIBLE
                            startActivity(Intent(this@SignUpActivity, MainActivity::class.java).also {
                                it.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
                            })
                            //finish()
                        }
                        override fun onError(e: Throwable) {
                            binding.progressBar.visibility = View.INVISIBLE
                            Toast.makeText(this@SignUpActivity, "${e.message}", Toast.LENGTH_SHORT)
                                .show()
                        }
                        override fun onComplete() {}
                    })
                }
                //注册失败
                override fun onError(e: Throwable) {
                    binding.progressBar.visibility = View.INVISIBLE
                    Toast.makeText(this@SignUpActivity, "${e.message}", Toast.LENGTH_SHORT).show()
                }
                //注册完成
                override fun onComplete() {}
            })
        }
    }

    //监听 EditText
    private val watcher = object : TextWatcher {
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            val t1 = binding.editTextUsername.text.toString().isNotEmpty()
            val t2 = binding.editTextPassword.text.toString().isNotEmpty()
            binding.buttonSignUpConfirm.isEnabled = t1 and t2
        }
        override fun afterTextChanged(s: Editable?) {}
    }
}

5. 主页实现

  5.1 添加词汇布局, new_word_dialog.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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/editText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:ems="10"
        android:hint="输入单词"
        android:inputType="textPersonName"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

  5.2 适配器布局 view_holder.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"
    android:layout_width="match_parent"
    android:layout_height="60dp">

    <TextView
        android:id="@+id/textViewContent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        android:text="TextView"
        android:textSize="24sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textViewTime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="32dp"
        android:text="TextView"
        android:textSize="18sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

  5.3 实现 Adapter 及 ViewHolder, MyAdapter.kt

class MyAdapter : Adapter<ViewHolder>() {
    private var _dataList = listOf<LCObject>()

    fun updateDataList(newList: List<LCObject>) {
        _dataList = newList
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val v = LayoutInflater.from(parent.context).inflate(R.layout.view_holder, parent, false)
        return object : ViewHolder(v) {}
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val obj = _dataList[position]
        holder.itemView.findViewById<TextView>(R.id.textViewContent).text =
            obj.get("word").toString()
        holder.itemView.findViewById<TextView>(R.id.textViewTime).text =
            obj.get("createdAt").toString()
    }
    
    override fun getItemCount(): Int = _dataList.size
}

  5.4 创建 ViewModel, MyViewModel.kt

class MyViewModel(application: Application) : AndroidViewModel(application) {
    private val _dataListLive = MutableLiveData<List<LCObject>>()
    val dataListLive: LiveData<List<LCObject>> = _dataListLive

    init {
        val query = LCQuery<LCObject>("Word")
        query.whereEqualTo("user", LCUser.getCurrentUser())
        query.findInBackground().subscribe(object : Observer<List<LCObject>> {
            override fun onSubscribe(d: Disposable) {}
            override fun onNext(t: List<LCObject>) {
                _dataListLive.value = t
            }
            override fun onError(e: Throwable) {
                Toast.makeText(application, "${e.message}", Toast.LENGTH_SHORT).show()
            }
            override fun onComplete() {}
        })

        //数据推送及同步
        val liveQuery = LCLiveQuery.initWithQuery(query)
        liveQuery.subscribeInBackground(object : LCLiveQuerySubscribeCallback() {
            //订阅成功
            override fun done(e: LCException?) {}
        })
        //推送回调
        liveQuery.setEventHandler(object : LCLiveQueryEventHandler() {
            //添加
            override fun onObjectCreated(LCObject: LCObject?) {
                super.onObjectCreated(LCObject)
                val t = _dataListLive.value?.toMutableList()
                t?.add(LCObject!!)
                _dataListLive.value = t
            }
            //删除
            override fun onObjectDeleted(objectId: String?) {
                super.onObjectDeleted(objectId)
                val t = _dataListLive.value?.toMutableList()
                val obj = t?.find {
                    it.get("objectId") == objectId
                }
                t?.remove(obj)
                _dataListLive.value = t
            }
            //更新
            override fun onObjectUpdated(LCObject: LCObject?, updateKeyList: MutableList<String>?) {
                super.onObjectUpdated(LCObject, updateKeyList)
                val obj = _dataListLive.value?.find {
                    it.get("objectId") == LCObject?.get("objectId")
                }
                updateKeyList!!.forEach {
                    obj?.put(it, LCObject?.get(it))
                }
                _dataListLive.value = _dataListLive.value
            }
        })
    }

    //添加表
    fun addWord(newWord: String) {
        LCObject("Word").apply {
            put("word", newWord)
            put("user", LCUser.getCurrentUser())
            saveInBackground().subscribe(object : Observer<LCObject> {
                override fun onSubscribe(d: Disposable) {}
                override fun onNext(t: LCObject) {
                    Toast.makeText(getApplication(), "添加成功", Toast.LENGTH_SHORT).show()
                }
                override fun onError(e: Throwable) {
                    Toast.makeText(getApplication(), "${e.message}", Toast.LENGTH_SHORT).show()
                }
                override fun onComplete() {}
            })
        }
    }
}

  5.5 主页布局 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="409dp"
        android:layout_height="729dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/floatingActionButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.915"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.976"
        app:srcCompat="@android:drawable/ic_input_add" />
</androidx.constraintlayout.widget.ConstraintLayout>

  5.6 实现事件,MainActivity.kt

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        viewModel = ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application))[MyViewModel::class.java]
        val myAdapter = MyAdapter()
        binding.recyclerView.apply {
            layoutManager = LinearLayoutManager(this@MainActivity)
            addItemDecoration(DividerItemDecoration(this@MainActivity, DividerItemDecoration.VERTICAL))
            adapter = myAdapter
        }
        viewModel.dataListLive.observe(this) {
            myAdapter.updateDataList(it)
            myAdapter.notifyDataSetChanged()
        }
        binding.floatingActionButton.setOnClickListener {
            addNewWord()
        }
    }

    //添加单词
    private fun addNewWord() {
        val view = LayoutInflater.from(this).inflate(R.layout.new_word_dialog, null)
        AlertDialog.Builder(this)
            .setTitle("添加")
            .setView(view)
            .setPositiveButton("确定") { _, _ ->
                val newWord = view.findViewById<EditText>(R.id.editText).text.trim().toString()
                viewModel.addWord(newWord)
            }
            .setNegativeButton("取消") { dialog, _ ->
                dialog.dismiss()
            }
            .show()
    }

    //退出
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        if (item.itemId == R.id.menuLogout) {
            LCUser.logOut()
            startActivity(Intent(this, LoginActivity::class.java))
            finish()
        }
        return super.onOptionsItemSelected(item)
    }

    //菜单项
    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.menu, menu)
        return super.onCreateOptionsMenu(menu)
    }
}

6. 效果图

      

   

猜你喜欢

转载自blog.csdn.net/u011193452/article/details/128457461