【问题标题】:Using Cookies with Retrofit and Hilt and recommended architecture将 Cookie 与 Retrofit 和 Hilt 以及推荐的架构一起使用
【发布时间】:2021-08-29 04:54:41
【问题描述】:

我对 Android 和 Java / Kotlin 还很陌生,所以我一直在努力在推荐的架构中实现 cookie。我查看了很多地方,阅读了文档并观看了很多视频,每个人都有不同的实现方式,我仍然感到困惑。它们是如何组合在一起的?

【问题讨论】:

    标签: android kotlin cookies retrofit dagger-hilt


    【解决方案1】:

    我原以为这是一个如此常见的用例,以至于我不敢相信答案并非遍布整个网络,但我必须努力将所有部分组合在一起。以下是从存储库向下对我有用的方法。我没有包括数据库方面的东西,因为这在很多地方都有很好的记录,我发现它很容易理解(如果有人需要我包括,请告诉我)。我中途切换到 Kotlin,因为我只能在 Kotlin 中找到部分答案。我的示例是登录用户并获取基本的个人资料详细信息。

    存储库将登录详细信息发送到服务器并将响应保存在数据库中,然后提取该信息以另存为 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)
                    }
                }
            }
        }
    }
    

    改造端点定义登录过程

    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
    }
    

    实体 - 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
        )
    }
    

    改造类以允许包含 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
        }
    }
    

    Hilt Module 将它们结合在一起

    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()
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-04-11
      • 2015-12-12
      • 1970-01-01
      • 1970-01-01
      • 2011-06-04
      • 2015-11-13
      • 2013-01-11
      相关资源
      最近更新 更多