pick an image save and show, ACTION_OPEN_DOCUMENT error - android

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.

Related

Launch Activity from onClick method of Listview (Fragment)

I recently started learning Android development in Kotlin. I did follow this guide and everything went good.
Now I'm trying to merge the content of those two guides:
https://developer.android.com/training/contacts-provider/retrieve-details.html#kotlin
and
https://developer.android.com/guide/components/fragments#Example
in order to display the details of a Contact using Fragments. I'm having trouble to launch an activity in the onItemClick method (the guide uses a ListView):
override fun onItemClick(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
// Get the Cursor
val cursor: Cursor? = (parent.adapter as? CursorAdapter)?.cursor?.apply {
// Move to the selected contact
moveToPosition(position)
// Get the _ID value
contactId = getLong(Companion.CONTACT_ID_INDEX)
// Get the selected LOOKUP KEY
//contactKey = getString(CONTACT_KEY_INDEX)
mContactKey = getString(Companion.CONTACT_KEY_INDEX)
// Create the contact's content Uri
contactUri = ContactsContract.Contacts.getLookupUri(contactId, mContactKey)
/*
* You can use contactUri as the content URI for retrieving
* the details for a contact.
*/
}
val intent = Intent().apply{
setClass(activity,DetailsActivity::class.java)
putExtra("contactID",contactId)
putExtra("mContackKey",mContactKey)
putExtra("contactUri",contactUri)
}
startActivity(intent)
}
If I create the Intent to start the activity as displayed in the guide, I get the compiler error "Inferred type is FragmentActivity?, but context was expected".
I changed then the Intent to either one of the following:
val intent = Intent().apply{
setClass(requireContext(),DetailsActivity::class.java)
putExtra("contactID",contactId)
putExtra("mContackKey",mContactKey)
putExtra("contactUri",contactUri)
}
startActivity(intent)
or
val intent = Intent().apply{
context?.let { setClass(it,DetailsActivity::class.java) }
putExtra("contactID",contactId)
putExtra("mContackKey",mContactKey)
putExtra("contactUri",contactUri)
}
startActivity(intent)
whit this, I do not get the compiler error, but in the Logcat I see the notice "W/ActivityThread: handleWindowVisibility: no activity for token android.os.BinderProxy#9dc9013"
Can you please point me to the correct way to instantiate an activity from the onClick method of a ListView inside a Fragment ? Thank you!
On a related note: do you recommend those guides or is their content obsolete ?
Edit: the full fragment class is here
Try below code for opening new activity from fragment :
activity?.let{
val intent = Intent (it, DetailsActivity::class.java)
intent.putExtra("contactID",contactId);
intent.putExtra("mContackKey",mContactKey);
intent.putExtra("contactUri",contactUri);
it.startActivity(intent)
}
startActivity() is a function in the Context class (of which Activity is a subclass), not of the Fragment class.
So you can make a direct bare call to startActivity() from within the code of a subclass of Context (as any Activity implementation is), but when you are calling it from a Fragment, you have to call it on a context: context.startActivity().
The Fragment.context property is nullable, so you can use requireContext().startActivity(). It won't be null when responding to clicks, so this is safe.

i can't make a contact between the activities in Kotlin

Hello this is my first app with kotlin i am trying to make annual rate calculation app the problem is i have 4 activities every activity own button and edit's texts
i wan't when The User click the button, the program get the numbers from Edit's texts and only make the calculation and save it somewhere and same work for the activity 2 and 3.
but when he click the last button of the last activity i want to call all the results and show it in ViewText
The Question is:How to save data Every time somewhere and call when i need it?
First Activity
class st {
var int_P: Double? = null
var ctl_P: Double? = null
public constructor(int_P: Any, ctl_P: Any) {
this.int_P = int_P.toString().toDouble() //Physique
this.ctl_P = ctl_P.toString().toDouble()
public fun GetMP(): Double {
return (this.int_P!! + (this.ctl_P!! * 2)) / 3
}
}
Btn_Next1.setOnClickListener ({
var int_P = java.lang.Double.parseDouble(edit_IP.text.toString()) //Physique
var ctl_P = java.lang.Double.parseDouble(edit_CP.text.toString())
var ss = st(int_P,ctl_P)
val ic = Intent(this, Sec_Act::class.java)
startActivity(ic)
})
(Secend and Third Activity Same)
Activity 4
btn1.setOnClickListener ({
var act1 = MainActivity.st().GetMC()
Textv.text = act1.toString()
})
With this method i got problem (no value passed for parameter int_P , ctl_P)
There are many different ways to send information back to an Activity:
onActivityResult(),
having a singleton class,
use Shared Preferences,
headless fragments,
sqlite database,
store the information in a file.
Intents
receivers
You need to determine which will be the best solution for you. Whether it's kotlin or java, the methodology will be the same.

How to read a series of items under a child node in Firebase using FirebaseAdapter

As the title states, I'm trying to read a bunch of nodes under a nested node in Firebase, and display the information using FirebaseAdapter. I'm using the parseSnapshot method to try and grab the information I need but I think I'm misunderstanding exactly how to go about getting the information. The user section of the database is structured like so:
I want only the information under UserInfo, and so I currently have the following code setup to intialize a recyclerview adapter (which gets the information)
private fun setupRequiredRecyclerView() {
val requiredItems = private_items_recycler
val context = this
val userDataRef = mDatabaseReference.child("Users/${prefs.UID}/UserInfo")
val mAdapter = RequiredItemsAdapter(User::class.java, R.layout.privaterecyclerview_item_row, RequiredProfileItemsViewHolder::class.java, userDataRef, context)
//load data into adapter
requiredItems.adapter = mAdapter
//add divider between items
requiredItems.addItemDecoration(Utilities.createDivider(this))
}
But the data snapshot I get back in the parseSnapshot method only seems to contain "dateJoined" and no other nodes, I'm guessing there's something wrong with my reference, but I don't know how to structure it - going up to "Users/UID" gets me everything but it also gets me UserInfoComplete, which I don't want (and as far as I know, there's no way to ignore that data in parseSnapshot, as FirebaseAdapter grabs every child node)
Does anyone out there know how exactly I need to structure my database reference to only get the UserInfo data?
(If necessary, this is my current parseSnapshot method):
override fun parseSnapshot(snapshot: DataSnapshot?): User {
lateinit var user : User
Log.i("Snapshot Data", snapshot!!.value.toString())
var dateJoined = snapshot!!.value
var dateOfBirth = snapshot.child("dateOfBirth").value
var gender = snapshot.child("Gender").value
var location = snapshot.child("Location").value
var phoneNumber = snapshot.child("phoneNumber").value
Log.i("Snapshot Data", snapshot!!.value.toString())
user.dateJoined = dateJoined as Long
return user
}

How to click on Android Gallery with Espresso

We currently have an Android application that we are testing with Espresso. One of the features we want to test is selecting a picture/image from the local image gallery. We can get all the way to bringing up the Gallery view, but then cannot select from Recent, Downloads, Gallery in the resulting window. A snippet as to how we got as far as we did is included below.
public void testShouldBeAbleToSelectPhotoFromGallery() {
getActivity();
// given
onView(withId(launch_gallery_button)).perform(click());
onView(withText("Gallery")).perform(click()); // this is a button in our app
// then we get stuck :(
}
Thanks!
This is not possible with with either Robotium or Espresso, as they only work on Activities of the app under test.
To write integration tests that work across different apps and Android built-in apps, you can use the UiAutomator framework provided by Google.
Basically you would analyse the gallery app in the uiautomatorview to learn how to select the ui elements your test case needs and then act on them, not unlike Espresso.
If you want to test that functionality in your app you should use the intent mocking functionality in Espresso.
Espresso tests should not leave your app in the first place.
Instead you catch the intent you use to open the gallery app and return a result back to your app.
During the test you will stay in your app, you will get a result immediately.
To do this check the intending and intended api's of Espresso.
Here is a tutorial by Pengj to get you acquainted with intent mocking. The tutorial mentions Mockito but you can perfectly use this without it.
The best and proper way is to use Espresso Intents. So you need to add the dependency in your app's build.gradle
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
In my case I was openning the gallery from a button within my app, then the code for the test and the addition of the intending and intended api's of Espresso as follows:
#Test
fun photos_CreationGalleryClickUI() {
savePickedImage()
val imgGalleryResult = createImageGallerySetResultStub()
intending(hasAction(Intent.ACTION_CHOOSER)).respondWith(imgGalleryResult)
onView(withId(R.id.photos_button_gallery)).perform(click())
onView(withId(R.id.photos_bigimage_viewer)).check(matches(hasImageSet()))
}
Here the matcher for intending is the key when the gallery needs to be open and avoid to manually pick an image:
hasAction(Intent.ACTION_CHOOSER)
I' using two helpers:
savePickedImage() to mock an image from the gallery
private fun savePickedImage() {
val bm = BitmapFactory.decodeResource(mActivityTestRule.activity.resources, R.mipmap.ic_launcher)
assertTrue(bm != null)
val dir = mActivityTestRule.activity.externalCacheDir
val file = File(dir?.path, "myImageResult.jpeg")
System.out.println(file.absolutePath)
val outStream: FileOutputStream?
try {
outStream = FileOutputStream(file)
bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream)
outStream.flush()
outStream.close()
} catch (e: FileNotFoundException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
}
And createImageGallerySetResultStub to stubbing the result after "picking" the image. Here putting the result as an array of parcelables was key, without it, the never result was never recognized:
private fun createImageGallerySetResultStub(): Instrumentation.ActivityResult {
val bundle = Bundle()
val parcels = ArrayList<Parcelable>()
val resultData = Intent()
val dir = mActivityTestRule.activity.externalCacheDir
val file = File(dir?.path, "myImageResult.jpeg")
val uri = Uri.fromFile(file)
val myParcelable = uri as Parcelable
parcels.add(myParcelable)
bundle.putParcelableArrayList(Intent.EXTRA_STREAM, parcels)
resultData.putExtras(bundle)
return Instrumentation.ActivityResult(Activity.RESULT_OK, resultData)
}
hasImageSet() as a matcher helper that checks if the imageView has or not a drawable:
return item.getDrawable() == null
NOTE: Remember to set the grant rule to avoid problems with the permissions and to define your test rule as an IntentTestRule (which extends from ActivityTestRule already)
#get:Rule
var mActivityTestRule = IntentsTestRule(AuctionCreationActivity::class.java)
#get:Rule
var mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
The dependency:
androidTestImplementation "androidx.test:rules:$testRules"

android - get a resource (a string) from its unique integer

I want to do the following:
I want to make a very simple gallery application. So I'd like to select a path for the images and set it as a resource. I set it in String.xml.
So I have another class, which needs the selected path to load all the images from it.
class ImageHolder
{
public ImageHolder()
{
this(R.string.image_dir);
//problem is here - R.string.image_dir returns a unique int, while what I really need is the string. How can I get it...
}
public ImageHolder(String path)
{
.........standart procedure.....
}
}
Use getString(resID) but you'll need a Context Object though.
It's a Function of Context, so within an Activity you can write this.getString(R.string.image_dir); or you can skip this altogether...

Categories

Resources