kotlin 使用带有Retrofit和Hilt的Cookie以及推荐的架构

htrmnn0y  于 2023-01-05  发布在  Kotlin
关注(0)|答案(1)|浏览(185)

我对Android和Java /Kotlin还是个新手,所以我一直在努力在推荐的架构中实现cookie。我看了很多地方,读了文档,看了很多视频,每个人都有如此不同的实现方式,我仍然感到困惑。这些是如何结合在一起的呢?

s4n0splo

s4n0splo1#

我本以为这是一个如此常见的使用案例,以至于我无法相信答案不在网络上到处都是,但我不得不努力工作,把所有的碎片放在一起。下面是从存储库开始对我起作用的方法。我没有包括数据库方面的东西,因为它在许多地方都有很好的记录,而且我发现它很容易理解(如果有人需要我包括这一点,请告诉我)。我在部分过程中切换到了Kotlin,因为我只能在Java中找到答案的某些部分。我的示例是登录用户并获取基本的配置文件详细信息。
存储库将登录详细信息发送到服务器并将响应保存在数据库中,然后提取该信息以另存为LiveData

package com.example.myapplication

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.myapplication.*
import com.example.myapplication.asDomainModel
import com.example.myapplication.asDBEntity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.IOException
import javax.inject.Inject

class LoginRepository @Inject constructor(
    private val myDao: MyDao,
    private val myNetwork: Network
) {

    private val _profile: MutableLiveData<Profile> = MutableLiveData()
    val profile: LiveData<Profile>
        get() = _profile

    suspend fun login(name: String, password: String) {

        withContext(Dispatchers.IO) {

            // log in to server and get profile data
            val profileNWEntity = myNetwork.login("login", name, password)

            // process response
            when (profileNWEntity.status) {
                "PROFLOGINOK" -> {
                    // save profile in database then retrieve
                    myDao.insertProfile(profileNWEntity.asDBEntity())
                    _profile.postValue(myDao.getProfile(profileNWEntity.user).asDomainModel())
                }
                else -> {
                    throw IOException (profileNWEntity.status)
                }
            }
        }
    }
}

Retrofit端点定义登录过程

package com.example.myapplication

import com.example.myapplication.ProfileNWEntity
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST

interface Network  {

    @FormUrlEncoded
    @POST("server_api")
    suspend fun login(
        @Field("action") action: String,
        @Field("name") name: String,
        @Field("pass") password: String
    ): ProfileNWEntity
}

Entity -Gson用来解析网络响应,存储库用来适应数据库

package com.example.myapplication

import com.example.myapplication.AccountDBEntity
import com.example.myapplication.ProfileDBEntity

/**
 * Base profile response from network query
 */
data class ProfileNWEntity(
    val user: Int,
    val name: String,
    val status: String
)

// map the profile from network to database format
fun ProfileNWEntity.asDBEntity(): ProfileDBEntity {
    return ProfileDBEntity(
        id = user,
        name = name
    )
}

Retrofit类以启用包含cookie(连同下面包含的拦截器,这来自tsuharesu和Nikhil Jha在https://gist.github.com/nikhiljha/52d45ca69a8415c6990d2a63f61184ff上的工作)

package com.example.myapplication

import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Inject

class RetrofitWithCookie @Inject constructor(
    context: Context, // uses Hilt to inject the context to be passed to the interceptors
    gson: Gson
) {
    private val mContext = context
    private val gson = gson

    fun createRetrofit(): Retrofit {
        val client: OkHttpClient
        val builder = OkHttpClient.Builder()
        builder.addInterceptor(AddCookiesInterceptor(mContext)) // VERY VERY IMPORTANT
        builder.addInterceptor(ReceivedCookiesInterceptor(mContext)) // VERY VERY IMPORTANT
        client = builder.build()

        return Retrofit.Builder()
            .baseUrl("myServer URL") // REQUIRED
            .client(client) // VERY VERY IMPORTANT
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build() // REQUIRED
    }
}

接收拦截器捕获入站cookie并将其保存在sharedpreferences中

package com.example.myapplication

import android.content.Context
import androidx.preference.PreferenceManager
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException
import java.util.*

// Original written by tsuharesu
// Adapted to create a "drop it in and watch it work" approach by Nikhil Jha.
// Just add your package statement and drop it in the folder with all your other classes.
class ReceivedCookiesInterceptor(context: Context?) : Interceptor {
    private val context: Context?
    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalResponse = chain.proceed(chain.request())
        if (!originalResponse.headers("Set-Cookie").isEmpty()) {
            val cookies = PreferenceManager.getDefaultSharedPreferences(context)
                .getStringSet("PREF_COOKIES", HashSet()) as HashSet<String>?
            for (header in originalResponse.headers("Set-Cookie")) {
                cookies!!.add(header)
            }
            val memes = PreferenceManager.getDefaultSharedPreferences(context).edit()
            memes.putStringSet("PREF_COOKIES", cookies).apply()
            memes.commit()
        }
        return originalResponse
    }

    init {
        this.context = context
    } // AddCookiesInterceptor()
}

AddCookies拦截器将cookie添加回将来的请求中

package com.example.myapplication

import android.content.Context
import androidx.preference.PreferenceManager
import dagger.hilt.android.qualifiers.ActivityContext
import okhttp3.Interceptor
import okhttp3.Response
import timber.log.Timber
import java.io.IOException
import java.util.*

// Original written by tsuharesu
// Adapted to create a "drop it in and watch it work" approach by Nikhil Jha.
// Just add your package statement and drop it in the folder with all your other classes.
/**
 * This interceptor put all the Cookies in Preferences in the Request.
 * Your implementation on how to get the Preferences may ary, but this will work 99% of the time.
 */
class AddCookiesInterceptor(@ActivityContext context: Context?) : Interceptor {
    // We're storing our stuff in a database made just for cookies called PREF_COOKIES.
    // I reccomend you do this, and don't change this default value.
    private val context: Context?
    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val builder = chain.request().newBuilder()
        val preferences = PreferenceManager.getDefaultSharedPreferences(context).getStringSet(
            PREF_COOKIES, HashSet()
        ) as HashSet<String>?

        // Use the following if you need everything in one line.
        // Some APIs die if you do it differently.
        /*String cookiestring = "";
        for (String cookie : preferences) {
            String[] parser = cookie.split(";");
            cookiestring = cookiestring + parser[0] + "; ";
        }
        builder.addHeader("Cookie", cookiestring);
        */for (cookie in preferences!!) {
            builder.addHeader("Cookie", cookie)
            Timber.d("adding cookie %s", cookie)
        }
        return chain.proceed(builder.build())
    }

    companion object {
        const val PREF_COOKIES = "PREF_COOKIES"
    }

    init {
        this.context = context
    }
}

将其连接在一起的手柄模块

package com.example.myapplication

import android.content.Context
import com.example.myapplication.Network
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton

@InstallIn(SingletonComponent::class)
@Module
class NetworkModule {

    @Singleton
    @Provides
    fun provideNetwork(retrofit: Retrofit)
        : Network = retrofit.create(Network::class.java)

    @Singleton
    @Provides
    fun provideRetrofitWithCookie(
        @ApplicationContext context: Context,
        gson: Gson
    ): Retrofit = RetrofitWithCookie(context, gson).createRetrofit()

    @Singleton
    @Provides
    fun provideGson(): Gson = GsonBuilder()
        .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") // used for parsing other responses
        .create()
}

相关问题