I have written a benchmark method to get benchmark report on specific activity. My plan is to first do this portion properly. And then adding other operation like clicking on a button and test how frame timing metric works. As I have converted code into MVVM pattern so main goal is to compare with previous code.
Written method:
#get:Rule
val benchmarkRule = MacrobenchmarkRule()
#Test
fun startupEntryActivity() = benchmarkRule.measureRepeated(
packageName = "io.demo",
metrics = listOf(FrameTimingMetric()),
iterations = 5,
startupMode = StartupMode.COLD,
) {
pressHome()
val intent = Intent()
intent.setPackage("io.demo")
intent.action = "io.demo.views.splash.SplashIntroSelectionActivity"
startActivityAndWait(intent)
}
But its getting error in test. Its leaving me without any clue
Error: Activity not started, unable to resolve Intent { act=io.demo.views.splash.SplashIntroSelectionActivity flg=0x10008000 pkg=io.demo }
Need to know where its getting wrong or am doing it wrong
sorry for my English
in my app I want to add an imageView, when pressed the user can pick an image and assign it to a book, save it and show it
so in my add book activity I added a registerForActivityResult (since i saw start for activity result is deprecated), and added an on click listener, here is the code
private var imageUri: Uri? = null
private val imagePicker =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
uri?.let {
imageUri = it
binding.imageView.setImageURI(imageUri)
}
}
image view on click
binding.imageView.setOnClickListener {
imagePicker.launch("image/*")
}
add book function
private fun addBook() {
// fetching info
val name: String = binding.nameEditText.text.toString()
val author: String = binding.authorEditText.text.toString()
val desc: String = binding.descEditText.text.toString()
//val imgurl: String = binding.imgurlEditText.text.toString()
// create book
val book = Book(name, author, desc, imageUri)
// adding book
bookViewModel.addToAllBooks(book)
// showing message
Toast.makeText(this, "Book has been added", Toast.LENGTH_SHORT).show()
}
(this function is set to the on click of an add button)
im using Room db to hold all the books, and one property of the book is the uri (which I added type converter to string using Uri.parse(stringUri)) which I later show in a recycler view and in activity that show one book, I'm using Glide to load the uri (not sure if necessary)
when I leaving the add book activity to the main activity that has fragment that show all book in a recycler view, the image is loaded correctly, but when I click on a book to open book activity I get this error (which does not crash my app but my image view shows nothing)
opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{4ffae1 26378:com.example.mynewlibrary/u0a136} (pid=26378, uid=10136) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
here is the code for loading the image
private fun initView(book: Book) {
// changing app title to book name capitelized
title = book.name.lowercase()
.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
binding.nameTextView.text = book.name
binding.authorTextView.text = book.author
binding.descTextView.text = book.desc
Glide.with(this).load(book.imageUri).into(binding.imageView)
}
(same method is used in the recycler view which works)
I have another question which might affect this one, I don't want if the user delete the image from the galley the image will be removed from my app, I'm not sure how it works (and I would have tried it if not the above error), if that how it works I would like to save the image in the app storage so even when deleted the app still has it image. if you know how to do it I will be happy to hear (or even only the method name and will do my research)
which I later show in a recycler view and in activity that show one book
If you use the uri later or in another activity then your right to read the content behind that uri is gone. The right does not persist.
So instead of ACTION_GET_CONTENT use ACTION_OPEN_DOCUMENT.
Then in onActivityResult() take persistable uri permission in order to .. persist your read/write access to that uri.
ActivityResultContracts.GetContent()
That equals ACTION_GET_CONTENT.
Background
I'm creating some SDK library, and I want to offer some liveData as a returned object for a function, that will allow to monitor data on the DB.
The problem
I don't want to reveal the real objects from the DB and their fields (like the ID), and so I wanted to use a transformation of them.
So, suppose I have this liveData from the DB:
val dbLiveData = Database.getInstance(context).getSomeDao().getAllAsLiveData()
What I did to get the liveData to provide outside, is:
val resultLiveData: LiveData<List<SomeClass>> = Transformations.map(
dbLiveData) { data ->
data.map { SomeClass(it) }
}
This works very well.
However, the problem is that the first line (to get dbLiveData) should work on a background thread, as the DB might need to initialize/update, and yet the Transformations.map part is supposed to be on the UI thread (including the mapping itself, sadly).
What I've tried
This lead me to this kind of ugly solution, of having a listener to a live data, to be run on the UI thread:
#UiThread
fun getAsLiveData(someContext: Context,listener: OnLiveDataReadyListener) {
val context = someContext.applicationContext ?: someContext
val handler = Handler(Looper.getMainLooper())
Executors.storageExecutor.execute {
val dbLiveData = Database.getInstance(context).getSomeDao().getAllAsLiveData()
handler.post {
val resultLiveData: LiveData<List<SomeClass>> = Transformations.map(
dbLiveData) { data ->
data.map { SomeClass(it) }
}
listener.onLiveDataReadyListener(resultLiveData)
}
}
}
Note: I use simple threading solution because it's an SDK, so I wanted to avoid importing libraries when possible. Plus it's quite a simple case anyway.
The question
Is there some way to offer the transformed live data on the UI thread even when it's all not prepared yet, without any listener ?
Meaning some kind of "lazy" initialization of the transformed live data. One that only when some observer is active, it will initialize/update the DB and start the real fetching&conversion (both in the background thread, of course).
The Problem
You are an SDK that has no UX/UI, or no context to derive Lifecycle.
You need to offer some data, but in an asynchronous way because it's data you need to fetch from the source.
You also need time to initialize your own internal dependencies.
You don't want to expose your Database objects/internal models to the outside world.
Your Solution
You have your data as LiveData directly from your Source (in this particular, albeit irrelevant case, from Room Database).
What you COULD do
Use Coroutines, it's the preferred documented way these days (and smaller than a beast like RxJava).
Don't offer a List<TransformedData>. Instead have a state:
sealed class SomeClassState {
object NotReady : SomeClassState()
data class DataFetchedSuccessfully(val data: List<TransformedData>): SomeClassState()
// add other states if/as you see fit, e.g.: "Loading" "Error" Etc.
}
Then Expose your LiveData differently:
private val _state: MutableLiveData<SomeClassState> = MutableLiveData(SomeClassState.NotReady) // init with a default value
val observeState(): LiveData<SomeClassState) = _state
Now, whoever is consuming the data, can observe it with their own lifecycle.
Then, you can proceed to have your fetch public method:
Somewhere in your SomeClassRepository (where you have your DB), accept a Dispatcher (or a CoroutineScope):
suspend fun fetchSomeClassThingy(val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default) {
return withContext(defaultDispatcher) {
// Notify you're fetching...
_state.postValue(SomeClassState.Loading)
// get your DB or initialize it (should probably be injected in an already working state, but doesn't matter)
val db = ...
//fetch the data and transform at will
val result = db.dao().doesntmatter().what().you().do()
// Finally, post it.
_state.postValue(SomeClassState.DataFetchedSuccessfully(result))
}
}
What else I would do.
The fact that the data is coming from a Database is or should be absolutely irrelevant.
I would not return LiveData from Room directly (I find that a very bad decision on Google that goes against their own architecture that if anything, gives you the ability to shoot your own feet).
I would look at exposing a flow which allows you to emit values N times.
Last but not least, I do recommend you spend 15 minutes reading the recently (2021) published by Google Coroutines Best Practices, as it will give you an insight you may not have (I certainly didn't do some of those).
Notice I have not involved a single ViewModel, this is all for a lower layer of the architecture onion. By injecting (via param or DI) the Dispatcher, you facilitate testing this (by later in the test using a Testdispatcher), also doesn't make any assumption on the Threading, nor imposes any restriction; it's also a suspend function, so you have that covered there.
Hope this gives you a new perspective. Good luck!
OK I got it as such:
#UiThread
fun getSavedReportsLiveData(someContext: Context): LiveData<List<SomeClass>> {
val context = someContext.applicationContext ?: someContext
val dbLiveData =
LibraryDatabase.getInstance(context).getSomeDao().getAllAsLiveData()
val result = MediatorLiveData<List<SomeClass>>()
result.addSource(dbLiveData) { list ->
Executors.storageExecutor.execute {
result.postValue(list.map { SomeClass(it) })
}
}
return result
}
internal object Executors {
/**used only for things that are related to storage on the device, including DB */
val storageExecutor: ExecutorService = ForkJoinPool(1)
}
The way I've found this solution is actually via a very similar question (here), which I think it's based on the code of Transformations.map() :
#MainThread
public static <X, Y> LiveData<Y> map(
#NonNull LiveData<X> source,
#NonNull final Function<X, Y> mapFunction) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
#Override
public void onChanged(#Nullable X x) {
result.setValue(mapFunction.apply(x));
}
});
return result;
}
Do note though, that if you have migration code (from other DBs) on Room, it might be a problem as this should be on a background thread.
For this I have no idea how to solve, other than trying to do the migrations as soon as possible, or use the callback of "onCreate" (docs here) of the DB somehow, but sadly you won't have a reference to your class though. Instead you will get a reference to SupportSQLiteDatabase, so you might need to do a lot of manual migrations...
I have state capturing test which needs to test something like this:
init{
loadValues();
}
fun loadValues(){
setStateToLoadingHere();
try{
val result: List<Anything> = getValues();
setStateToSuccessHere(result);
}catch(Exception e){
setStateToErrorHere();
}
}
I want to ignore this getValues() method call to be able to test the loading state only. Is there a way where I can ignore this whole try catch? If I don't do that I'll get NPE inside my test.
Try to use spy().
val spy = spy(YouClass())
doNothing().when(spy).setStateToSuccessHere(any())
doNothing().when(spy).setStateToErrorHere(any())
spy.loadValues()
I want to learn do add, insert, update, and delete on cloud firestore.
I already read cloud firestore documentation but i dont understand it since im really new to Could firestore and i just started learing android studio.
I already make the constructor and planning to use ListView to read the data and Delete and Update on the setOnLongClickListener with Intent to make editing easier. And i use another activity for the add function.
Most of the tutorial i meet are putting all in 1 place and make it harder to understand it.
And mixing code that i got from different resource making the code harder to understand and it look weird.
So what is the easy to understand code to do this with this database?
https://i.stack.imgur.com/ngnR7.png
You should require user authentication if you are going to be using the Firebase Android SDK to post data to your server and then your firebase.rules on your server should check that caller has right level of access.
Get an instance of FirebaseFirestore as in
private val firestore: FirebaseFirestore
get() = FirebaseFirestore.getInstance()
documents on Firebase always follow the pattern document/collection/document/collection/... example val docName = "animalDoc/mammalCollection/rodentDoc/miceCollection/JerryDoc
get all mice:
firestore.collection("animalDoc/mammalCollection/"
+"rodentDoc/miceCollection").get()
.addOnSuccessListener { result -> //result is just a Kotlin collection
val myFavoriteMouse = result.find { it["name"] == "Jerry" }
// do something with Jer
}
Set a mouse
val docName = "animalDoc/mammalCollection/rodentDoc/miceCollection/JerryDoc"
firestore.document(docName).set(mapOfData).addOnCompleteListener {
if (it.isSuccessful) {
// log your success or whatever
} else {
// log your failure or whatever
}
}
Update a mouse
val docName = "animalDoc/mammalCollection/rodentDoc/miceCollection/JerryDoc"
val docRef = firestore.document(docName)
firestore.runTransaction { transaction ->
transaction.update(docRef, "color", "brown")
}
Delete a mouse
val docName = "animalDoc/mammalCollection/rodentDoc/miceCollection/JerryDoc"
firestore.document(docName).delete()