Giter Club home page Giter Club logo

ksprefs's Introduction

KsPrefs

Secure SharedPreferences

Download from MavenCentral Codacy Kotlin Android Min Sdk Android Compile Version License

Gradle
dependencies {
    implementation 'com.github.cioccarellia:ksprefs:2.4.1'
}
Kotlin DSL
dependencies {
    implementation("com.github.cioccarellia:ksprefs:2.4.1")
}
Maven
<dependency>
    <groupId>com.github.cioccarellia</groupId>
    <artifactId>ksprefs</artifactId>
    <version>2.4.1</version>
    <type>pom</type>
</dependency>
  • Powerful SharedPreferences wrapper & API.
  • Easy to pick up & use right away.
  • Tested and production-ready.
  • Fully customizable behaviour.
  • Built-in cryptography & decoding engines (PlainText, Base64, AES-CBC, AES-ECB, Android KeyStore + AES-GCM / RSA KeyPair).
  • Extensive type & enum support.
val prefs = KsPrefs(applicationContext)
val count = prefs.pull<Int>("app_start_key")

To read from SharedPreferences, use pull(key, fallback).
To write to SharedPreferences, use push(key, value).

Introduction



KsPrefs (Kotlin Shared Preferences) is a wrapper for the default Android SharedPreferences (SP for short) implementation.
Its purposes are to bring security to preference storage through cryptography, to implement an elegant and practical SP API, and to do so with as little overhead as possible.
Ksprefs can be used as a replacement of direct SP usage, which lacks both security and practicality, and which even Google is moving away from with Jetpack DataStore.
On top of the SP API, KsPrefs extends with numerous features and extra bits which come pre-packed with the library, and can be used to enhance the development experience and productivity.

Basics

Initialization

You should create KsPrefs only once in your codebase.

val prefs = KsPrefs(applicationContext)

It is recommended to keep it inside your Application class, so that it's reachable everywhere from your code.

class App : Application() {

    companion object {
        lateinit var appContext: Context
        val prefs by lazy { KsPrefs(appContext) }
    }

    override fun onCreate() {
        super.onCreate()
        appContext = this
    }
}

Terminology

Term Description
SP Android Shared Preferences
Entry Key-Value pair stored by SP. Thus the basic data entity which can be pushed and pulled
Persistent XML Storage SP XML file containing actual entries. Stored in the application private storage

Read (Pull)

To retrieve saved values from SP you use pull().
Key uniquely identifies a record, fallback is the default value if none is found in SP.

val uname = prefs.pull(key = "username", fallback = nobody)

There are 4 different variants of pull.

  • pull<T>(key, fallback<T>): Scans the preferences with the given key. If a record is found, the value is read from the persistent XML storage, deserialized as the requested type and returned. If the key isn't contained inside the storage, the fallback value is returned.
  • pull<T>(key): No fallback value is supplied
  • pull<T>(key, kclass<T>)
  • pull<T>(key, jclass<T>)

A function is defined safe when you supply the fallback (Android SharedPreferences defines it default) value, so that, for any given key, you always have a concrete in-memory value to return.
A function is unsafe because there is no guarantee it will return a concrete value, as it only relies on the supplied key to pull the value from the persistent XML storage

Even though the standard SharedPreferences API always forces you to provide a default (KsPrefs defines it fallback) value, KsPrefs allows you to leave that out, because supplying an actual instance of an object, in some contexts is verbose and redundant if you are know that the key is present inside the persistent storage, or if for some clever intuition you know that the key holds a value at some specific time.

val username = prefs.pull("username")

📌 #1: Using an unsafe version of pull() isn't dangerous, as long as you are sure the target key holds a value.
📌 #2: The 3 unsafe functions accept the type parameter as a kotlin class, as a java class or as a kotlin generic. For the latter, the bytecode of the function is inlined, in order for the generic type to be reified.

Write (Push)

To save values to the preference storage you use push()
Push takes a key and a value, and stores them inside the preferences, according to the commitStrategy, autoSavePoliciy.

prefs.push("username", viewModel.username)

Configuration

KsPrefs is configurable at initialization time with specific parameters.
Each parameters has a default value which will be used unless you specify otherwise.
Each parameter changes the internal behaviour and the algorithms used, so it is vital to choose the appropriate settings.

val prefs = KsPrefs(applicationContext) {
    // Configuration Parameters Lambda
    encryptionType = PlainText()
    autoSave = AutoSavePolicy.MANUAL
    commitStrategy = CommitStrategy.COMMIT
}
Field Type Description Default Value
encryptionType EncryptionType Encryption technique used to encrypt and decrypt data PlainText()
commitStrategy CommitStrategy Strategy to use at the moment of writing preferences entries to the persistent XML storage CommitStrategy.APPLY
autoSave AutoSavePolicy Whether after a push() operation changes are saved to the persistent XML storage; saving strategy depending on commitStrategy AutoSavePolicy.AUTOMATIC
mode Int SharedPreferences access mode Context.MODE_PRIVATE
charset Charset Charset used for string-to-byte and byte-to-string conversions Charsets.UTF_8
keyRegex Regex? Regular Expression which, if non null, every key must match. null

Saving, Auto Save Policies & Commit Strategies

A pending transaction is a change which is registered in-memory, but not yet on the XML preference file. Android SharedPreferences works that way; indeed, you can stack up pending transactions, but at some point you have to actually save them. If the app were to shut down unexpectedly, those changes would be lost.
To commit any pending transaction to the persistent XML storage, in ksprefs you use save(). This matches commit() and apply() SharedPreferences behaviour you may be accustomed to.

Auto Save Policy

By default, autoSave is set to AutoSavePolicy.AUTOMATIC, and therefore changes are automatically synchronized with the underlying XML file, because after each push() call, a save() follows, in order to automatically commit and save the preference. Therefore, no pending transaction is kept.

However, if autoSave is turned off (using AutoSavePolicy.MANUAL), push() will save the change in-memory, but is not going to write it to the XML preferences file until save() is invoked. This way it's going to create a pending transaction which will be kept in-memory until a save() operation happens.

Here is a table representing when values are saved to the storage, depending on the policy in use.

AutoSavePolicy AUTO MANUAL
push()
queue()
save()
SharedPreferences.Editor.commit()
SharedPreferences.Editor.apply()

📌 AutoSavePolicy chooses when to write changes to the persistent XML storage and when to keep them in memory.

Commit Strategy

The best (and default) practise while dealing with SharedPreferences is to use APPLY. It is asynchronous and fast. COMMIT is also available, though it should not be used unless you have a valid reason to, given its synchronous and strict nature, as well as NONE, for no-op (Does not save anything, used internally for queue()).
save() and push() always refer to the commit strategy to decide how to save their updates to the persistent XML preference storage.

Here is a table representing various features of different commit strategies. Check out the official documentation here and see this post for more intel.

CommitStrategy APPLY COMMIT NONE
in-memory
XML
async
atomic
error report

📌 The CommitStrategy involves how to write changes to the persistent XML storage.

Queuing

To enqueue values to be written into the preference storage you use queue(). It follows push()'s syntax.
While push, by default, pushes the update immediately on the XML persistent storage (By default, changeable with AutoSave), queue() saves the update in-memory without writing it out to the storage.
Not writing the changes to the file makes enqueuing a valid choice for both batch computing or resource-expensive and long-running operations.

  • queue() takes a key and a value, and saves the changes in-memory.
  • queue() does not actually send updates to the storage. You can do so by calling save() (Or by using push() subsequently).

This segment touches a broader concept, which is storing scope. There are two storing scopes for SharedPreferences:

  • in-memory (key-value pairs are kept in memory). This is fast to read to/write from, but does not persist application restarts.
  • XML (key-value pairs are kept on a file). Writing to a file is mildly expensive but it allows preferences to survive across application restarts.
    Here is a table explaining how different methods inside KsPrefs touch and go through those storing scopes.
StoringScope in-memory XML
push(k, v) ✅ (By default)
queue(k, v)
save()

📌 The StoringScope determines at which level changes are propagated.

In the following snippet (Given that autoSavePolicy is set to AUTOMATIC), n in-memory and x XML write operations are performed. This, given f(n) and f(x) for how long those operations will take, takes n×f(n) + m×f(x). Given that, if using push(), m=n, then it resolves to n×(f(n) + f(x))

for ((index, pic) in picsArray.toList().withIndex()) {
    // Long-running computation
    prefs.push("pic-$index", pic.url)
}

Even though this isn't a significant speedup for small data sizes, as n (and m) grow the computation takes longer; since enqueuing values sets m=1, thus, f(n) < f(x). The time/op chart follows a much more gentle curve: n×f(n) + f(x). This improvements drastically optimizes performances for a large amount of operations.

for ((index, pic) in picsArray.toList().withIndex()) {
    // Long-running computation
    prefs.queue("pic-$index", pic.url)
}

// One save operation
prefs.save()

Please note, that if you set autoSavePolicy to MANUAL, push() will only change the in-memory values, and you will need to save them manually anyways.

API

KsPrefs provides some customizable data structures, to abstract preference reads/writes to function calls.

Preferences Center

A PrefsCenter is to be though as a task-specific abstractor. It is used to enclose and contain all the SP-specific operations, such as providing a key, doing some value specific post-read/pre-write operation, providing the fallback value or the explicit return type, handling logic / conditions and interacting with other app components.

object StartCounterPrefCenter : PrefsCenter(App.prefs) {
    private const val counterKey = "start_counter"
    
    fun increment() = prefs.push(counterKey,  read() + 1)
    fun read() = prefs.pull(counterKey, 0)
}

Dynamic Delegates

It is really useful and fun to have dynamic properties whose value is a direct representation of what the underlying XML preferences file contains.

val accentColor by prefs.dynamic("accent_color", "#2106F3")

When you set a value for this property, it is also updated on the XML preference file, as it is a dynamic reference to the preference.

Encryption

KsPrefs provides with different levels of encryption. From no encryption at all (EncryptionType.PlainText and EncryptionType.Base64), to standard AES with key size among 128, 192 & 256-bit and ECB/CBC modes (base64-wrapped ciphertext), to Android's own keystore system (though the ksprefs implementation isn't nearly as fast as the AES-backed one).
It is recommended to store the AES key into a native library shipped with your application, which makes reverse engineering your code harder.

Sample App

ksprefs's People

Contributors

cioccarellia avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ksprefs's Issues

[Feature Request] Add support for pullOrNull

Sometimes it would really ease the job if there was a method like pullOrNull(key: String): T?. The only workaround I've found is something like this

    @Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
    inline fun <reified T : Any> pullOrNull(key: String): T? = if (prefs.exists(key)) {
        prefs.pull(key)
    } else {
        null
    }

but obviously I would like to avoid this :)

java.security.InvalidKeyException: Key for algorithm RSA not suitable for symmetric encryption on API < 23

java.lang.RuntimeException: Unable to create application <censored>: com.cioccarellia.ksprefs.exceptions.EngineException: 
        at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4521)
        at android.app.ActivityThread.access$1500(ActivityThread.java:144)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1339)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:135)
        at android.app.ActivityThread.main(ActivityThread.java:5221)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
     Caused by: com.cioccarellia.ksprefs.exceptions.EngineException: 
        at com.cioccarellia.ksprefs.exceptions.EngineException$Companion.convertFrom(EngineException.kt:34)
        at com.cioccarellia.ksprefs.extensions.ExceptionExtsKt.getOrThrowException(ExceptionExts.kt:23)
        at com.cioccarellia.ksprefs.internal.SafeRun$DefaultImpls.runSafely(SafeRun.kt:26)
        at com.cioccarellia.ksprefs.engines.base.Engine.runSafely(Engine.kt:25)
        at com.cioccarellia.ksprefs.internal.SafeRun$DefaultImpls.runSafely$default(SafeRun.kt:24)
        at com.cioccarellia.ksprefs.engines.model.keystore.RsaKeyPairKeyStoreEngine.encrypt(RsaKeyPairKeyStoreEngine.kt:68)
        at com.cioccarellia.ksprefs.engines.model.keystore.RsaKeyPairKeyStoreEngine.derive-qJcYHfg(RsaKeyPairKeyStoreEngine.kt:61)
        at com.cioccarellia.ksprefs.enclosure.KspEnclosure.read$library(KspEnclosure.kt:100)
        at com.cioccarellia.ksprefs.dispatcher.KspDispatcher.pull(KspDispatcher.kt:73)
        at com.cioccarellia.ksprefs.KsPrefs.pull(KsPrefs.kt:152)
        at com.cioccarellia.ksprefs.delegates.dynamic.DelegateDynamicKsPref.getValue(DelegateDynamicKsPref.kt:30)
Caused by: java.security.InvalidKeyException: Key for algorithm RSA not suitable for symmetric enryption.
        at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(BaseBlockCipher.java:464)
        at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(BaseBlockCipher.java:758)
        at javax.crypto.Cipher.init(Cipher.java:661)
        at javax.crypto.Cipher.init(Cipher.java:621)
        at com.cioccarellia.ksprefs.extensions.CipherExtsKt.initEncryptKeyPair(CipherExts.kt:60)
        at com.cioccarellia.ksprefs.engines.model.keystore.RsaKeyPairKeyStoreEngine.getEncryptionCipher(RsaKeyPairKeyStoreEngine.kt:52)
        at com.cioccarellia.ksprefs.engines.model.keystore.RsaKeyPairKeyStoreEngine.access$getEncryptionCipher$p(RsaKeyPairKeyStoreEngine.kt:32)
        at com.cioccarellia.ksprefs.engines.model.keystore.RsaKeyPairKeyStoreEngine$encrypt$1.invoke(RsaKeyPairKeyStoreEngine.kt:70)
        at com.cioccarellia.ksprefs.engines.model.keystore.RsaKeyPairKeyStoreEngine$encrypt$1.invoke(RsaKeyPairKeyStoreEngine.kt:32)

My config:

KsPrefs(context) {
    this.encryptionType = EncryptionType.KeyStore("prefs")
}

I've read through the other issues about using the AndroidKeyStore and how it works differently on API < 23. I'm assuming that the library handles all that internally - Am I misusing it?

Add enum-to-byte conversion

This is a really cool feature as it can be nice for settings screens and configuration-related stuff.
I can implement this in 2 ways.

  • Index-based: If an enum has 3 elements, we store as a binary representation its index.
  • name-based: If an enum has 3 elements, we store as a binary representation its name.

Maybe array typo

I'm not sure about that, but I think you do have a typo in this array.
I think it should be "abcdefghijklmnopqrstuvwxyz123456789".

Observable property general listener does not use primitive key to access storage

Observed callbacks are not being invoked correctly while using an EncryptionType different than plaintext, because the key is confronted with the key inside the observedPrefs map, and those wto keys have a different grade.

// Works for plaintext, fails for base64, aes and keystore
val observedPref = observedPrefs[key] ?: return@OnSharedPreferenceChangeListener

Integrating the incoming key will match the one inside the map.

javax.crypto.IllegalBlockSizeException when pulling value consecutively

I'm using ksprefs library and from time to time i get javax.crypto.IllegalBlockSizeException
I've found that if 2 threads or 2 calls are made at the same time the app crashes with the above exception.
I can easily recreate the crash using these lines on samsung galaxy tab:

    for (i in 1..10){
        lifecycleScope.launch(Dispatchers.Default){
            val isEnabled = ZoeyApp.prefs.pull(booleanKey, true)
            Log.d("Testing", isEnabled.toString())
        }
    }

I have the prefs set us as such:

val prefs by lazy { KsPrefs(context) {
   encryptionType = EncryptionType.KeyStore("prefs")
}}

Thank you very much for the great lib!

Experiencing bugs around pull

The basic stacktrace is as following

com.cioccarellia.ksprefs.exceptions.EngineException: 
 at com.cioccarellia.ksprefs.exceptions.EngineException$Companion.convertFrom(EngineException.kt:34)
 at com.cioccarellia.ksprefs.extensions.ExceptionExtsKt.getOrThrowException(ExceptionExts.kt:23)
 at com.cioccarellia.ksprefs.internal.SafeRun$DefaultImpls.runSafely(SafeRun.kt:26)
 at com.cioccarellia.ksprefs.engines.base.Engine.runSafely(Engine.kt:25)
 at com.cioccarellia.ksprefs.internal.SafeRun$DefaultImpls.runSafely$default(SafeRun.kt:24)
 at com.cioccarellia.ksprefs.engines.model.keystore.AesKeyStoreEngine.decrypt(AesKeyStoreEngine.kt:77)
 at com.cioccarellia.ksprefs.engines.model.keystore.AesKeyStoreEngine.integrate-Wp7FQgo(AesKeyStoreEngine.kt:51)
 at com.cioccarellia.ksprefs.enclosure.KspEnclosure.read$library(KspEnclosure.kt:111)
 at com.cioccarellia.ksprefs.dispatcher.KspDispatcher.pull(KspDispatcher.kt:73)
 at com.cioccarellia.ksprefs.KsPrefs.pull(KsPrefs.kt:152)
 at ---------------.ui.fragment.HomeFragment.onViewCreated(HomeFragment.kt:119)
 at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:332)
 at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1199)
 at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2236)
 at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2009)
 at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1965)
 at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1861)
 at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
 at android.os.Handler.handleCallback(Handler.java:883)
 at android.os.Handler.dispatchMessage(Handler.java:100)
 at android.os.Looper.loop(Looper.java:237)
 at android.app.ActivityThread.main(ActivityThread.java:7948)
 at java.lang.reflect.Method.invoke(Native Method)
 at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1075)
Caused by: javax.crypto.IllegalBlockSizeException
 at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:519)
 at javax.crypto.Cipher.doFinal(Cipher.java:2055)
 at com.cioccarellia.ksprefs.engines.model.keystore.AesKeyStoreEngine$decrypt$1.invoke(AesKeyStoreEngine.kt:78)
 at com.cioccarellia.ksprefs.engines.model.keystore.AesKeyStoreEngine$decrypt$1.invoke(AesKeyStoreEngine.kt:34)
 ... 23 more
Caused by: android.security.KeyStoreException: Invalid operation handle
 at android.security.KeyStore.getKeyStoreException(KeyStore.java:1550)
 at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.update(KeyStoreCryptoOperationChunkedStreamer.java:132)
 at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:217)
 at android.security.keystore.AndroidKeyStoreAuthenticatedAESCipherSpi$BufferAllOutputUntilDoFinalStreamer.doFinal(AndroidKeyStoreAuthenticatedAESCipherSpi.java:373)
 at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:506)
 ... 26 more

My configuration at app start is:

val prefs by lazy {
    KsPrefs(appContext = instance) {
        encryptionType = EncryptionType.KeyStore("prefs")
        commitStrategy = CommitStrategy.COMMIT
    }
}

Different initialization classes

The default KsPref constructor accepts a base class which is manually configurable by the user to tune preferences for its service.

A nice idea would be to create different constructors with a different class parameter to infer config data

Adding dependency to gradle

Hey, i am getting an error when i try to add the
implementation 'com.cioccarellia.ksprefs:2.0.0-rc1' to the gradle.

It says -> ERROR: Failed to resolve: com.cioccarellia.ksprefs:2.0.0-rc1:
Any idea why this is happening?

Exists is broken when encryption is enabled

In KspEnclosure the exists method doesn't use deriveKey which breaks the method in case of encryption is enabled.

This

    internal fun exists(
        key: String
    ) = sharedReader.exists(key)

should be

    internal fun exists(
        key: String
    ) = sharedReader.exists(deriveKey(key))

Tested with 2.2.4 and 2.2.5

Create secondary engines

Allow engines to have sub-engines to run microtasks or device-dependent operations, to keep the main engine clear and nice. Maybe a nicer name may come in handy.

Add support for AndroidKeyStore key storage

In your README, you've suggested using cipher.so, but we could also use the AndroidKeyStore to:

  • Store the symmetric key for API >= 23
  • Store an asymmetric keypair to wrap a symmetric key for 18 < API < 23

I'd be happy to submit a pull request for it. This would allow users of this library to get a much enhanced layer of security for free!

java.lang.NullPointerException

Thanks for providing this library.

When I released new version of my android app which was using this library,
App gets huge crashes because of the following function.

Caused by java.lang.NullPointerException
com.cioccarellia.ksprefs.extensions.ReaderExtsKt.readOrThrow (ReaderExts.kt:34)
com.cioccarellia.ksprefs.enclosure.KspEnclosure.readUnsafe$library (KspEnclosure.kt:69)
com.cioccarellia.ksprefs.dispatcher.KspDispatcher.pull (KspDispatcher.kt:88)

Looking forward to hear from you.
Thanks.

Cipher decryption data is with a wrong padding (BadPaddingException)

Hi, I'm getting this intermittent error while getting accessToken saved in CryptoPrefs. I have no control of the accessToken value saved to the preference as it's coming from the API, do you guys have any idea why it's happening?

Fatal Exception: com.andreacioccarelli.cryptoprefs.exceptions.CryptoPreferencesException: Cipher decryption data is with a wrong padding (BadPaddingException). input = [�K2|E��D��F��0p�XL��e��9� �-���::���A�8�t��O?�OF�u���l]�h]�{5�f�A�t���g�] javax.crypto.BadPaddingException: error:1e000065:Cipher functions:OPENSSL_internal:BAD_DECRYPT
       at com.andreacioccarelli.cryptoprefs.wrappers.PrefsEncrypter.finalize + 108(PrefsEncrypter.kt:108)
       at com.andreacioccarelli.cryptoprefs.wrappers.PrefsEncrypter.decrypt + 89(PrefsEncrypter.kt:89)
       at com.andreacioccarelli.cryptoprefs.CryptoWrapper.get + 35(CryptoWrapper.kt:35)
       at com.andreacioccarelli.cryptoprefs.CryptoPrefs.get + 46(CryptoPrefs.kt:46)
       at co.sleekr.sleekrhr.core.retrofit.NetworkBuilder$authenticator$1.invokeSuspend + 117(NetworkBuilder.kt:117)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith + 33(ContinuationImpl.kt:33)
       at kotlinx.coroutines.DispatchedTask.run + 233(Dispatched.kt:233)
       at kotlinx.coroutines.EventLoopImplBase.processNextEvent + 116(EventLoop.kt:116)
       at kotlinx.coroutines.BlockingCoroutine.joinBlocking + 76(Builders.kt:76)
       at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking + 53(Builders.kt:53)
       at kotlinx.coroutines.BuildersKt.runBlocking + 1(:1)
       at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default + 35(Builders.kt:35)
       at kotlinx.coroutines.BuildersKt.runBlocking$default + 1(:1)
       at co.sleekr.sleekrhr.core.retrofit.NetworkBuilder.authenticator + 99(NetworkBuilder.kt:99)
       at co.sleekr.sleekrhr.core.retrofit.NetworkBuilder.access$authenticator + 33(NetworkBuilder.kt:33)
       at co.sleekr.sleekrhr.core.retrofit.NetworkBuilder$mySleekrHttpClient$$inlined$let$lambda$1.authenticate + 89(NetworkBuilder.kt:89)
       at okhttp3.internal.http.RetryAndFollowUpInterceptor.followUpRequest + 288(RetryAndFollowUpInterceptor.java:288)
       at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept + 158(RetryAndFollowUpInterceptor.java:158)
       at okhttp3.internal.http.RealInterceptorChain.proceed + 147(RealInterceptorChain.java:147)
       at okhttp3.internal.http.RealInterceptorChain.proceed + 121(RealInterceptorChain.java:121)
       at co.sleekr.sleekrhr.core.retrofit.NetworkBuilder$queryParameter$1.intercept + 173(NetworkBuilder.kt:173)
       at okhttp3.internal.http.RealInterceptorChain.proceed + 147(RealInterceptorChain.java:147)
       at okhttp3.internal.http.RealInterceptorChain.proceed + 121(RealInterceptorChain.java:121)
       at co.sleekr.sleekrhr.core.retrofit.NetworkBuilder$headerInterceptor$1.intercept + 145(NetworkBuilder.kt:145)
       at okhttp3.internal.http.RealInterceptorChain.proceed + 147(RealInterceptorChain.java:147)
       at okhttp3.internal.http.RealInterceptorChain.proceed + 121(RealInterceptorChain.java:121)
       at okhttp3.logging.HttpLoggingInterceptor.intercept + 213(HttpLoggingInterceptor.java:213)
       at okhttp3.internal.http.RealInterceptorChain.proceed + 147(RealInterceptorChain.java:147)
       at okhttp3.internal.http.RealInterceptorChain.proceed + 121(RealInterceptorChain.java:121)
       at co.sleekr.sleekrhr.core.retrofit.NetworkBuilder$checkConnectivityInterceptor$1.intercept + 187(NetworkBuilder.kt:187)
       at okhttp3.internal.http.RealInterceptorChain.proceed + 147(RealInterceptorChain.java:147)
       at okhttp3.internal.http.RealInterceptorChain.proceed + 121(RealInterceptorChain.java:121)
       at okhttp3.RealCall.getResponseWithInterceptorChain + 200(RealCall.java:200)
       at okhttp3.RealCall$AsyncCall.execute + 147(RealCall.java:147)
       at okhttp3.internal.NamedRunnable.run + 32(NamedRunnable.java:32)
       at java.util.concurrent.ThreadPoolExecutor.runWorker + 1162(ThreadPoolExecutor.java:1162)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run + 636(ThreadPoolExecutor.java:636)
       at java.lang.Thread.run + 764(Thread.java:764)

here's how i try to get the accessToken:
sharedPref.pref().get(REFRESH_TOKEN, emptyString())

pref() returns CryptoPref

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.