Having trouble with initialising sharedPreferences in object - android

I am quite new to Kotlin & Android Studio.
I have a object RetrofitClient which I am trying to import sharedpreferences for URL, username etc
However I am struggling with getting it to work
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitClient {
private val AUTH = "Basic " + Base64.encodeToString("A1122334".toByteArray(),Base64.NO_WRAP)
private const val BASE_URL = "http://192.168.2.5:3001/"
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder()
.addHeader("Authorization", AUTH)
.method(original.method(),original.body())
val request = requestBuilder.build()
chain.proceed(request)
}.build()
val httpinstance: httpApi by lazy{
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
retrofit.create(httpApi::class.java)
}
}
Saved Preferences Class:
import android.content.Context
class LoginSavedPreferences(context: Context) {
private val sharedPreferences = context.getSharedPreferences("LoginPreferences",0)
// Save Boolean
fun saveBooleanPref(key: String, value: Boolean) {
sharedPreferences.edit().putBoolean(key,value).apply()
}
// get Boolean
fun getBooleanPref(key: String): Boolean {
return sharedPreferences.getBoolean(key,false)
}
// Save String
fun saveStringPref(key: String, value: String) {
sharedPreferences.edit().putString(key,value).apply()
}
// get String
fun getStringPref(key: String): String? {
return sharedPreferences.getString(key,null)
}
// Save Int
fun saveIntPref(key: String, value: Int) {
sharedPreferences.edit().putInt(key,value).apply()
}
// get Int
fun getIntPref(key: String): Int {
return sharedPreferences.getInt(key,0)
}
}
Here is a snippet from my LoginActivity, which works, but when I past the two lines into the RetrofitClient it will not initialse
class LoginActivity : AppCompatActivity() {
private lateinit var loginSavedPreferences: LoginSavedPreferences /// THIS LINE
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
loginSavedPreferences = LoginSavedPreferences(applicationContext) /// AND THIS LINE
// put values in the text boxes if we have them
val savedserveraddress = loginSavedPreferences.getStringPref(LoginConstants.KEY_SERVERADDRESS)
val savedusername = loginSavedPreferences.getStringPref(LoginConstants.KEY_USERNAME)
val savedpassword = loginSavedPreferences.getStringPref(LoginConstants.KEY_PASSWORD)

Try to change the lateinit var loginSavedPreferences to:
private val loginSavedPreferences: LoginSavedPreferences by lazy {
LoginSavedPreferences(this#LoginActivity)
}
and check what value you get in savedserveraddress with a breakpoint. If it still doesn't work try to change mode in getSharedPreferences to 1

Related

I want show a value from ViewModel to Textview in a fragment. Help me to do this

I want show "answerText.value" from MainViewModel to TextView in a fragment.
MainViewModel:
package com.example.intelligenttalkie
import android.annotation.SuppressLint
import android.view.View
import android.widget.EditText
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import okhttp3.OkHttpClient
import okhttp3.ResponseBody
import okhttp3.logging.HttpLoggingInterceptor
import org.json.JSONObject
import retrofit2.*
import retrofit2.converter.gson.GsonConverterFactory
#SuppressLint("StaticFieldLeak")
class MainViewModel : ViewModel() {
var answerText: MutableLiveData<String> = MutableLiveData()
private lateinit var editText: EditText
companion object {
fun onGenerateButtonClick(mainViewModel: MainViewModel, editText: EditText) {
mainViewModel.editText = editText
// Get the user's question from the EditText
val question = mainViewModel.editText.text.toString()
// Send the request to the API
mainViewModel.generateText(question)
}
}
#Suppress("UNCHECKED_CAST")
private fun generateText(prompt: String) {
// Create a logger for debugging purposes
val logger = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val client = OkHttpClient.Builder()
.addInterceptor(logger)
.build()
// Configure the Retrofit object
val retrofit = Retrofit.Builder()
.baseUrl("https://api.openai.com/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
// Create an instance of the OpenAIService
val service = retrofit.create(OpenAIService::class.java)
// Set the request parameters
val request = mapOf(
"model" to "text-davinci-003",
"prompt" to prompt,
"temperature" to 0,
"max_tokens" to 182,
"top_p" to 1,
"frequency_penalty" to 0,
"presence_penalty" to 0,
"stop" to "###"
)
// Make the API request
service.generateText(request = request as Map<String, String>).enqueue(object :
Callback<ResponseBody> {
override fun onResponse(
call: Call<ResponseBody>,
response: Response<ResponseBody>
) {
if (response.isSuccessful) {
val responseBody = response.body()
val responseString = responseBody!!.string()
val jsonResponse = JSONObject(responseString)
val choices = jsonResponse.getJSONArray("choices").getJSONObject(0)
val text = choices.getString("text")
answerText.value = text.trim()
} else {
answerText.value = response.errorBody()?.string()
}
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
answerText.value = t.message
}
})
}
}
I want to display the answerText.value textview in result fragment. i tried many things but its not showing, I'm just beginner and apologize for bad code or if any errors in code
I tried to data bind in fragment layout but it shows nothing but printing in log
You'll need to observe the answerText object and update the TextView whenever the value changes. Here's an example you can use to display answerText: -
class ResultFragment : Fragment() {
private lateinit var answerText: TextView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
answerText = view.findViewById(R.id.answer_text)
// Observe the answerText object and update the TextView whenever the value changes
answerText.observe(viewLifecycleOwner, Observer {
answerText.text = it
})
}
}

How to save retrofit response in shared preferences in and use it repository layer

I have post method where I am sending login request to server but first I want to save that response using shared preferences in my repository how can save retrofit response in shared preferences
below my interface class I have implemented login post request logic
interface MeloApi {
#Headers("Content-Type: application/json")
#POST("/login")
suspend fun makeLogin(#Body loginModel: LoginModel) : Response<LoginModel>
}
below my loginModel class
data class LoginModel(
val userName:String,
val password:String)
below LoginResponse.kt
data class LoginResponse(
#SerializedName("accessToken")
val accessToken: String,
#SerializedName("refreshToken")
val refreshToken: String,
#SerializedName("status")
val status: String,
#SerializedName("user")
val user: User
)
below my Interceptor
class HeaderInterceptor(
private val tokenManager: TokenManager
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
request = request.newBuilder()
.header("refreshToken", tokenManager.refreshToken.toString())
.header("accessToken", tokenManager.accessToken.toString())
.build()
return chain.proceed(request)
}
}
below my token manager
class TokenManager {
var accessToken: String? = null
var refreshToken: String? = null
}
below my appModule.kt
val apiModule = module {
single {
TokenManager()
}
single {
HeaderInterceptor(get())
}
single {
val httpInterceptor = HttpLoggingInterceptor()
httpInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
Retrofit.Builder()
.client(
OkHttpClient.Builder()
.addInterceptor(HeaderInterceptor(get()))
.addInterceptor(httpInterceptor).build()
)
// .addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.build()
.create(MeloApi::class.java)
}
}
below my LoginRepository
class LoginRepository(
private val meloApi: MeloApi
) {
suspend fun login(loginModel: LoginModel) {
GlobalScope.launch {
val response = meloApi.makeLogin(loginModel)
response.isSuccessful
val userName = response.body()?.userName
val password = response.body()?.password
loginRequest(userName, password)
}
}
private suspend fun loginRequest(userName: String?, password: String?){
}
}
If you want to store as object you have to use serialization/deserialization.
I used gson here.
val loginResponse = LoginResponse("AToken", "RToken", "200", User("Edgar"))
val objToString = Gson().toJson(loginResponse)
val sharedPref = context.getSharedPreferences("loginRes", MODE_PRIVATE)
sharedPref.edit().putString("response", objToString).apply()
val response = sharedPref.getString("response", "null")
val stringToObj = Gson().fromJson(response, LoginResponse::class.java)
You can use proto datastore instead of shared preferences.

How to change Retrofit baseUrl from shared preferences at runtime

I am trying to change the Retrofit baseUrl from SharedPreferences in my app at runtime, but the change is only implemented when I close and open the app. I have tried using onSharedPreferenceChangeListener() and onPreferenceChangeListener() but I still get the same result. How do I implement the listeners so that they change the baseUrl at runtime?
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.baseUrl(CompanyApiService .apiBaseUrl)
.build()
interface CompanyApiService {
#GET("employees")
fun getEmployeesAsync(): Deferred<List<Employees>>
#GET("title/{id}")
fun getTitlesAsync(#Path("id") id: Int): Deferred<List<Titles>>
#POST("message")
fun submitMessage(#Body message: Message): Call<String>
}
object CompanyApi {
val retrofitService: CompanyApiService by lazy {
retrofit.create(CompanyApiService ::class.java)
}
var apiBaseUrl = ""
}
MainActivity.kt
class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
...
PreferenceManager.setDefaultValues(this, R.xml.main_preference, false)
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)
sharedPrefs.registerOnSharedPreferenceChangeListener(this)
val apiBaseUrl = sharedPrefs.getString(KEY_PREF_BASE_URL, "")
CompanyApi.apiBaseUrl = apiBaseUrl!!
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == KEY_PREF_BASE_URL) {
val newApiBaseUrl = sharedPreferences?.getString(key, "")
CompanyApi.apiBaseUrl = newApiBaseUrl!!
}
}
object CompanyApi {
val retrofitService: CompanyApiService by lazy {
retrofit.create(CompanyApiService ::class.java)
}
This creates a singleton, you need to change that and re-create your Api when you changed your base_url however I wouldn't advise to do so. Creating a retrofit instance is consuming and you might get into errors later on.
Lucky for you Retrofit has a simple solution for that:
public interface UserManager {
#GET
public Call<ResponseBody> userName(#Url String url);
}
The URL String should specify the full Url you wish to use.
also, check this out -> enter link description here

Accessing sharedPreferences inside Retrofit Interceptor

Here is a Retrofit Interceptor used to inject automatically a token inside requests. I'm trying to get this token from sharedPreferences but getSharedPreferences is not available there.
How can i retrieve my token from sharedpreferences inside this Interceptor ?
import android.preference.PreferenceManager
import okhttp3.Interceptor
import okhttp3.Response
class ServiceInterceptor: Interceptor {
var token : String = "";
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
if(request.header("No-Authentication") == null){
if (request.url.toString().contains("/user/signin") === false) {
// Add Authorization header only if it's not the user signin request.
// Get token from shared preferences
val sharedPreference = PreferenceManager.getSharedPreferences()
token = sharedPreference.getString("token")
if (!token.isNullOrEmpty()) {
val finalToken = "Bearer " + token
request = request.newBuilder()
.addHeader("Authorization", finalToken)
.build()
}
}
}
return chain.proceed(request)
}
}
There's a simple solution for this in Kotlin – just copy & paste the code into a new AppPreferences.kt file and follow the 4 TODO steps outlined in the code:
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import androidx.core.content.edit
object AppPreferences {
private var sharedPreferences: SharedPreferences? = null
// TODO step 1: call `AppPreferences.setup(applicationContext)` in your MainActivity's `onCreate` method
fun setup(context: Context) {
// TODO step 2: set your app name here
sharedPreferences = context.getSharedPreferences("<YOUR_APP_NAME>.sharedprefs", MODE_PRIVATE)
}
// TODO step 4: replace these example attributes with your stored values
var heightInCentimeters: Int?
get() = Key.HEIGHT.getInt()
set(value) = Key.HEIGHT.setInt(value)
var birthdayInMilliseconds: Long?
get() = Key.BIRTHDAY.getLong()
set(value) = Key.BIRTHDAY.setLong(value)
private enum class Key {
HEIGHT, BIRTHDAY; // TODO step 3: replace these cases with your stored values keys
fun getBoolean(): Boolean? = if (sharedPreferences!!.contains(name)) sharedPreferences!!.getBoolean(name, false) else null
fun getFloat(): Float? = if (sharedPreferences!!.contains(name)) sharedPreferences!!.getFloat(name, 0f) else null
fun getInt(): Int? = if (sharedPreferences!!.contains(name)) sharedPreferences!!.getInt(name, 0) else null
fun getLong(): Long? = if (sharedPreferences!!.contains(name)) sharedPreferences!!.getLong(name, 0) else null
fun getString(): String? = if (sharedPreferences!!.contains(name)) sharedPreferences!!.getString(name, "") else null
fun setBoolean(value: Boolean?) = value?.let { sharedPreferences!!.edit { putBoolean(name, value) } } ?: remove()
fun setFloat(value: Float?) = value?.let { sharedPreferences!!.edit { putFloat(name, value) } } ?: remove()
fun setInt(value: Int?) = value?.let { sharedPreferences!!.edit { putInt(name, value) } } ?: remove()
fun setLong(value: Long?) = value?.let { sharedPreferences!!.edit { putLong(name, value) } } ?: remove()
fun setString(value: String?) = value?.let { sharedPreferences!!.edit { putString(name, value) } } ?: remove()
fun exists(): Boolean = sharedPreferences!!.contains(name)
fun remove() = sharedPreferences!!.edit { remove(name) }
}
}
Now from anywhere within your app you can get a value like this:
val heightInCentimeters: Int? = AppPreferences.heightInCentimeters
val heightOrDefault: Int = AppPreferences.heightInCentimeters ?: 170
Setting a value to the SharedPreferences is just as easy:
AppPreferences.heightInCentimeters = 160 // sets a new value
The above is extracted from my FitnessTracker project. See this file for a full example.
As coroutineDispatcher has commented you should pass in the shared preferences into the interceptor's constructor and hold a reference to them.
Try this:
class ServiceInterceptor(private val prefs: SharedPreferences): Interceptor {
val token: String get() = prefs.getString("token")
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
if(request.header("No-Authentication") == null){
if (request.url.toString().contains("/user/signin") === false) {
// Add Authorization header only if it's not the user signin request.
request = token
.takeUnless { it.isNullOrEmpty }
?.let {
request.newBuilder()
.addHeader("Authorization", "Bearer $it")
.build()
}
?: request
}
}
return chain.proceed(request)
}
}
The interceptor now takes in a reference to shared preferences so the dependency has been inverted and it can allow for easy testing by stubbing the passed SharedPreferences.
And it can be instanatiated like this:
ServiceInterceptor(PreferenceManager.getSharedPreferences())
You can create a singleton class for SharedPreference and then you can access it from any class you want.
Example
class SessionManager private constructor(context:Context) {
private val prefs:SharedPreferences
private val editor:SharedPreferences.Editor
var token:String
get() {
return prefs.getString("token", "")
}
set(token) {
editor.putString("token", token)
editor.apply()
}
init{
prefs = context.getSharedPreferences("Your_Preference_name", Context.MODE_PRIVATE)
editor = prefs.edit()
}
companion object {
private val jInstance:SessionManager
#Synchronized fun getInstance(context:Context):SessionManager {
if (jInstance != null)
{
return jInstance
}
else
{
jInstance = SessionManager(context)
return jInstance
}
}
}
}
Now you have to pass context in constructor of ServiceInterceptor and you can access SharedPreference like following.
val token = SessionManager.getInstance(context).token;
try this
val token = PreferenceManager.getSharedPreferences().getToken("","")
builder.addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder()
.addHeader("Authorization", "Bearer $token")
val request = requestBuilder.build()
chain.proceed(request)
}
return builder.build()
I needed to store JWT token in my application and I was struggling with the same issue and came up with a solution, it may be useful for some of you.
It's achievable with dependency injection using Dagger Hilt.
Inject ServiceInterceptor to RetrofitClient
class RetrofitClient #Inject constructor(
private val serviceInterceptor: ServiceInterceptor,
) {
val api: ApiInterface by lazy {
Retrofit.Builder()
.addConverterFactory(
GsonConverterFactory.create(
GsonBuilder().registerTypeAdapter(
LocalDate::class.java,
JsonDeserializer { json, _, _ ->
LocalDate.parse(
json.asJsonPrimitive.asString
)
}).create(),
)
)
.baseUrl(Constants.URL)
.client(
OkHttpClient.Builder()
.addInterceptor(OkHttpProfilerInterceptor())
.addInterceptor(serviceInterceptor)
.build()
)
.build()
.create(ApiInterface::class.java)
}
}
Inject SharedPreferences to ServiceInterceptor
class ServiceInterceptor #Inject constructor(
private val sharedPreferences: SharedPreferences,
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = sharedPreferences.getString("JWT_AUTH_TOKEN", "")
var request = chain.request()
if (request.header("No-Authentication") == null) {
if (token != null && token.isNotEmpty()) {
val bearerToken = "Bearer $token"
request = request.newBuilder()
.addHeader("Authorization", bearerToken)
.build()
}
}
return chain.proceed(request)
}
}
Define Module for dagger hilt:
#Module
#InstallIn(SingletonComponent::class)
object ConfigModule {
#Singleton
#Provides
fun provideSharedPreferences(#ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("JWT_AUTH_TOKEN", Context.MODE_PRIVATE)
#Singleton
#Provides
fun provideServiceInterceptor(sharedPreferences: SharedPreferences): ServiceInterceptor =
ServiceInterceptor(sharedPreferences)
#Singleton
#Provides
fun provideRetrofitClient(serviceInterceptor: ServiceInterceptor): RetrofitClient =
RetrofitClient(serviceInterceptor)
}
That's it, you should be good to go with injecting your RetrofitClient into your repository like that:
class CustomRepository #Inject constructor(
private val retrofitClient: RetrofitClient,
)
If you want to write to SharedPreferences from other classes just inject it:
#AndroidEntryPoint
class LoginUserFragment #Inject constructor(
private val sharedPreferences: SharedPreferences,
)
And then to write your JWT token to SharedPreferences use code:
with(sharedPreferences.edit()) {
putString("JWT_AUTH_TOKEN", token)
apply()
}

android- architecture component return nothing on android

I'm new in architecture component and kotlin.
I've written and app that works fine one android pre orio but it returns nothing on android orio and above .
these are my codes, this is my code for retrofit connection
object ApiConnection {
val BaseUrl ="http://site.ir/"
val client: Retrofit
get() {
val gson = GsonBuilder()
.setLenient()
.create()
///for Logging
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
var retrofit = Retrofit.Builder()
.baseUrl(BaseUrl)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client)
.build()
return retrofit
}
}
this is my code for activity :
lateinit var rc_peyk:RecyclerView;
lateinit var peykViewModel:PeykHistoryViewModel
lateinit var peykHistAdapter: PeykHistoryAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.peyk_history)
declare();
peykViewModel= ViewModelProviders.of(this).get(PeykHistoryViewModel::class.java)
peykViewModel.getPeyks(Func.getUid(this)).observe(this
, Observer<MutableList<Peyk_item>>{
if(it==null){
MyToast.makeText(this#PeykHistory, "null 1");
}
if(it!=null && it.size==0){
MyToast.makeText(this#PeykHistory, "size 0");
}
if(it!=null && it.size>0){
peykHistAdapter= PeykHistoryAdapter(this#PeykHistory,it)
rc_peyk.adapter=peykHistAdapter
}
})
actionbar()
}
private fun declare() {
rc_peyk=findViewById(R.id.rc_peyk_history)
rc_peyk.layoutManager=LinearLayoutManager(this)
}
this is the code view viewModel :
class PeykHistoryViewModel(application:Application):AndroidViewModel(application){
private val peykRepository:PeykHistoryRepository= PeykHistoryRepository()
private lateinit var peykHistory:MutableLiveData<MutableList<Peyk_item>>
fun getPeyks(uid:String):MutableLiveData<MutableList<Peyk_item>>{
if(!::peykHistory.isInitialized){
peykHistory=peykRepository.getPeyks(uid)
}
return peykHistory
}
this is the code for repository :
class PeykHistoryRepository {
private lateinit var getPeykHistory: getPeykHistoryApi
private lateinit var peykList: MutableLiveData<MutableList<Peyk_item>>
fun getPeyks(uid: String): MutableLiveData<MutableList<Peyk_item>> {
if (!::peykList.isInitialized) {
peykList = MutableLiveData()
}
getPeykHistory = ApiConnection.client.create(getPeykHistoryApi::class.java)
getPeykHistory.getPeykHistory(uid)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ result ->
if(result.peyks!=null){
peykList+=result.peyks
}else{
Log.v("this","nothing");
}
}, { error ->
Log.v("this",error.localizedMessage.toString())
})
return peykList;
}
operator fun <T> MutableLiveData<MutableList<T>>.plusAssign(values: List<T>) {
val value = this.value ?: arrayListOf()
value.addAll(values)
this.value = value
}
there is no error and the api is fine and return the correct json .
could you help me ? I've tried too many things but doesn't help me

Categories

Resources