I want to create an easier way to handle SharedPreferences.
The way I want to call it is like this
get preference:
val email = SharedPrefs.userdata.email
val wifiOnly = SharedPrefs.connections.wifiOnly
set preference:
SharedPrefs.userdata.email = "someone#example.com"
SharedPrefs.connections.wifiOnly = true
I'm able to do so like this:
App.instance returns a Context object in the following snippet
object SharedPrefs {
val userdata by lazy { UserPreferences() }
val connections by lazy { ConnectionPreferences() }
class UserPreferences {
private val prefs: SharedPreferences = App.instance.getSharedPreferences("userdata", Context.MODE_PRIVATE)
var email: String
get() = prefs.getString("email", null)
set(value) = prefs.edit().putString("email", value).apply()
}
class ConnectionPreferences {
private val prefs: SharedPreferences = App.instance.getSharedPreferences("connections", Context.MODE_PRIVATE)
var wifyOnly: Boolean
get() = prefs.getBoolean("wifiOnly", false)
set(value) = prefs.edit().putBoolean("wifyOnly", value).apply()
}
}
The problem is that this can still be called: SharedPrefs.UserPreferences()
Can I make this constructor private to this file or object only?
You can separate the interface and the implementation class, and make the latter private to the object:
object SharedPrefs {
val userdata: UserPreferences by lazy { UserPreferencesImpl() }
interface UserPreferences {
var email: String
}
private class UserPreferencesImpl : UserPreferences {
private val prefs: SharedPreferences =
App.instance.getSharedPreferences("userdata", Context.MODE_PRIVATE)
override var email: String
get() = prefs.getString("email", null)
set(value) = prefs.edit().putString("email", value).apply()
}
// ...
}
Alternatively, if you are developing a library or you have a modular architecture, you can make use of the internal visibility modifier to restrict the visibility to the module:
class UserPreferences internal constructor() { /* ... */ }
You can try something like this
class UserPreferences private constructor()
{
// your impl
}
This is the reference
Related
I have this class that exposes remote configs to others. I thought by creating a class, I would just mock it when testing others that use it but so far, firebase is blocking me. Not sure what I am doing wrong exactly.
class AppRemoteConfig #Inject constructor() {
private var remoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig
private fun setListeningInterval(): Long {
if (BuildConfig.DEBUG){
return 0;
}
return 86400;
}
init {
val configSettings = remoteConfigSettings {
minimumFetchIntervalInSeconds = setListeningInterval()
}
remoteConfig.setConfigSettingsAsync(configSettings)
remoteConfig.setDefaultsAsync(R.xml.remote_config_defaults)
remoteConfig.fetchAndActivate()
.addOnCompleteListener(OnCompleteListener{
if (it.isSuccessful) {
remoteConfig.activate()
}
})
}
fun getString(key: String): String {
return this.remoteConfig.getString(key)
}
}
Now a class uses it this way:
class GetRData #Inject constructor(
private val _remoteConfig: AppRemoteConfig
) {
operator fun invoke(key): String {
try {
return _remoteConfig.getString(key)
} catch(ex: Exception){
return ""
}
return ""
}
}
Now I want to test GetRData class but I get the error: Default FirebaseApp is not initialized in this process null. Make sure to call FirebaseApp.initializeApp(Context) first.
here is what I have tried:
class GetRDataTest {
private var appRemoteConfig = mockk<AppRemoteConfig>(relaxed = true)
private lateinit var getRData : GetRData
#Before
fun setUp(){
getRData = GetRData(appRemoteConfig)
}
#Test
fun `Should get string value`() {
every { appRemoteConfig.getString("status") } returns "red"
val result = getRData.invoke("status")
verify { appRemoteConfig.getString("status") }
Truth.assertThat(result).isEqualTo("red")
}
}
I want to get 100 items per page from ObjectBox database using Paging3 library. But I'm getting all the elements from database at once. The official document of ObjectBox have information about paging2.
Here is my Implementation:
LocalDatabaseImpl.kt
LocalDatabaseImpl(
// dependencies...
private val trxBox: Box<Trx>
) {
override fun getPagingDataSource(): ObjectBoxDataSource.Factory<Trx> {
val query = trxBox.query().build()
return ObjectBoxDataSource.Factory(query)
}
}
ViewModel.kt
#HiltViewModel
class HomeViewModel #Inject constructor(
private val db: LocalDatabase
): ViewModel() {
private val pager: Pager<Int, Trx>
get() = Pager(
config = PagingConfig(pageSize = 100),
pagingSourceFactory = db.getPagingDataSource()
.asPagingSourceFactory(Dispatchers.IO)
)
private var _trxFlow = pager.flow.cachedIn(viewModelScope)
val trxFlow: Flow<PagingData<Trx>> get() = _trxFlow
}
Inside Compose
#Composable
fun TrxContent() {
// ...
val trxItems = viewModel.trxFlow.collectAsLazyPagingItems()
// latest trx
LazyColumn () {
items(items = trxItems) { trx ->
if (trx == null) return#items
ItemTrxCompose(trx)
}
}
}
I was trying to do this, but it did not work
var stringList = mutableListOf<String>()
set(value) {
field = value
validateData()
}
somehow validateData() did not run
It actually did.
I'm assuming that what you really want it's to trigger validateData each time a String is added or removed from your list.
You can write a custom list using implementation by delegation[1]:
class MList(
private val list: MutableList<String> = mutableListOf(),
private val listener: () -> Unit
) : MutableList<String> by list {
override fun add(element: String) =
list.add(element).also { listener.invoke() }
override fun remove(element: String) =
list.remove(element).also { listener.invoke() }
...
}
And use it like this:
class Foo {
var stringList = MList { validateData() } // Now each time stringList.add() is called validateData will be triggered
private fun validateData(){ ... }
}
I request data from server by bunches and store it in the array.To track fetching of the next bunch of the data I have this class.In the addItems method I notify diffObservers and pass list of new items:
class PackItems:MutableLiveData<ArrayList<GetPacksResponse.PackData>>() {
private var diffObservers=ArrayList<Observer<List<GetPacksResponse.PackData>>>()
private var active=false
fun observeItems(owner: LifecycleOwner, valueObserver:Observer<List<GetPacksResponse.PackData>>,diffObserver:Observer<List<GetPacksResponse.PackData>>) {
super.observe(owner,valueObserver)
diffObservers.add(diffObserver)
}
override fun removeObservers(owner: LifecycleOwner) {
super.removeObservers(owner)
diffObservers= ArrayList()
}
fun addItems(toAdd:List<GetPacksResponse.PackData>) {
value?.addAll(toAdd)
if (active)
for (observer in diffObservers)
observer.onChanged(toAdd)
}
override fun onActive() {
super.onActive()
active=true
}
override fun onInactive() {
super.onInactive()
active=false
}
}
The problem is PackItems is MutableLiveData and it's not good practice to expose it.Is there way to cast it to LiveData?Like usually we do:
private val _items = MutableLiveData<List<Int>>()
val items: LiveData<List<Int>> = _items
UPD:Ideally would be if I could expose completely immutable LiveData.But I can't just write
private val _packs:PackItems=PackItems()
val packs:LiveData<ArrayList<GetPacksResponse.PackData>>
get()=_packs
Because in this case packs won't contain observeItems method.Therefore there must be custom class derived from LiveData like:
open class PackItems: LiveData<ArrayList<GetPacksResponse.PackData>>() {
protected var active=false
protected var diffObservers = ArrayList<Observer<List<GetPacksResponse.PackData>>>()
fun observeItems(owner: LifecycleOwner, valueObserver: Observer<List<GetPacksResponse.PackData>>, diffObserver: Observer<List<GetPacksResponse.PackData>>) {
super.observe(owner,valueObserver)
diffObservers.add(diffObserver)
}
//...
}
class MutablePackItems: PackItems() {
fun addItems(toAdd:List<GetPacksResponse.PackData>) {
value?.addAll(toAdd)
if (active)
for (observer in diffObservers)
observer.onChanged(toAdd)
}
}
But in this case I won't be able to set data because now MutablePackItems is LiveData(immutable) :)
I'd consider using composition instead of inheritance:
class PackItems() {
private val mutableData = MutableLiveData<ArrayList<GetPacksResponse.PackData>>()
val asLiveData: LiveData<ArrayList<GetPacksResponse.PackData>> get() = mutableData
...
fun observeItems(owner: LifecycleOwner, valueObserver:Observer<List<GetPacksResponse.PackData>>,diffObserver:Observer<List<GetPacksResponse.PackData>>) {
mutableData.observe(owner,valueObserver)
diffObservers.add(diffObserver)
}
fun removeObservers(owner: LifecycleOwner) {
mutableData.removeObservers(owner)
diffObservers = ArrayList()
}
// etc
}
EDIT: to set active as in your original code, may be a bit nastier:
private val mutableData = object : MutableLiveData<ArrayList<GetPacksResponse.PackData>>() {
override fun onActive() {
super.onActive()
active = true
}
override fun onInactive() {
super.onInactive()
active = false
}
}
EDIT 2:
but the main problem is I need to return custom LiveData class with custom observeItems method
The point is that you don't necessarily. Whenever you'd call LiveData's method (e.g. observe), just call items.asLiveData.observe(...) instead. If you want to pass it to another method foo accepting LiveData, call foo(items.asLiveData).
In principle, you could modify this approach by extending LiveData and delegating all calls to mutableData:
class PackItems(): LiveData<ArrayList<GetPacksResponse.PackData>>() {
private val mutableData = MutableLiveData<ArrayList<GetPacksResponse.PackData>>()
...
fun observeItems(owner: LifecycleOwner, valueObserver:Observer<List<GetPacksResponse.PackData>>,diffObserver:Observer<List<GetPacksResponse.PackData>>) {
mutableData.observe(owner,valueObserver)
diffObservers.add(diffObserver)
}
override fun observe(owner: LifecycleOwner, observer: ArrayList<GetPacksResponse.PackData>) {
mutableData.observe(owner, observer)
}
override fun removeObservers(owner: LifecycleOwner) {
mutableData.removeObservers(owner) // not super!
diffObservers = ArrayList()
}
// etc
}
but I don't think it's a good idea.
I have a ViewModel look like this.
class SignInViewModel(private val requestDataUseCase: RequestDataUseCase) : ViewModel() {
...
var isLoading = MediatorLiveData<Boolean>()
...
fun requestData(id: String) {
requestDataUseCase(id).let { liveData ->
isLoading.value = true
isLoading.addSource(liveData) {
it?.either(this::onSuccess, this::handleError)
isLoading.removeSource(liveData)
isLoading.value = false
}
}
}
...
}
My test class
class SignInViewModelTest {
private lateinit var signInViewModel: SignInViewModel
#Mock private lateinit var requestDataUseCase: RequestDataUseCase
private val dataResponse: MutableLiveData<String> = MutableLiveData()
#Before
fun setUp() {
signInViewModel = SignInViewModel(requestDataUseCase)
}
#Test
fun testRequestData() {
`when`(requestDataUseCase(any(), any())).thenReturn(dataResponse)
//trying to call
fun requestData("123456")
}
}
The problem is I got NullPointerException on isLoading variable in SignInViewModel class.
It's the variable that controls ProgressBar in the XML layout.
So my question is how can I mock the isLoading variable?
Or are there any suggestion on how should I create a test for this scenario?
Thank you in advance.