How to capture and display a picture with camera in Jetpack Compose - android-jetpack-compose

This seems to be so easy that I feel embarrassed to ask. However, I have been fighting it for hours and scratching my head.
The goal is to simply launch camera, if the button is pressed and camera permission has been granted, and then take a picture and display it in an Image composable with Coil. Each time I try this, the ActivityResultContract fails to save the image. I think something with the given URI is messy, but it's beyond my magical powers to solve it.
Long story short, here's my code. Hope someone has an idea of how to remedy this issue!
#Composable
fun MyPicker() {
val context = LocalContext.current
val permissionLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission().apply{
}){
}
val imgUri by remember{mutableStateOf("${context.filesDir}/temp.jpg".toUri())}
val captureLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.TakePicture()){
Toast.makeText(context, "Image capture: ${if(it) "Successful" else "Failed"}", Toast.LENGTH_SHORT)
.show()
}
Column {
Button(onClick = {
if(ContextCompat.checkSelfPermission(context, android.Manifest.permission.CAMERA) != PERMISSION_GRANTED)
permissionLauncher.launch(android.Manifest.permission.CAMERA)
else captureLauncher.launch(imgUri)
}) {
Text("Load")
}
Image(painter = rememberAsyncImagePainter(imgUri), null)
}
}
The error says different things for each Uri I provide. For example, for the above uri, it says the following:
E exception while saving result to URI: Optional.of(/data/user/0/com.mycompany.activityops/files/temp.jpg)
java.io.FileNotFoundException: No content provider: /data/user/0/com.mycompany.activityops/files/temp.jpg
And then I tried to, also, provide a Uri.fromFile("${context.filesDir}/temp.jpg"), which was also frowned upon by Android throwing a huge FileUriExposedException, and then I'm clueless, with my head wandering around storage, content resolvers, Uris, etc.

In a util file create a function which returns a temporary file
fun createImageFile(context: Context): File {
// Create an image file name
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"JPEG_${timeStamp}_", //prefix
".jpg", //suffix
storageDir //directory
)
}
Further get uri for the file
val file = Utils.createImageFile(context = context)
val imageUri = FileProvider.getUriForFile(
context,
"application_authority",
file
)
NOTE:
The path of the image will be
val path = file.absolutePath
When starting a result contract you need to send uri
val cameraLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.TakePicture(),
onResult = {isSuccess: Boolean ->
// Handle Result
}
cameraLauncher.launch(uri)
On getting a success call back, you can get the image inside file by
File(path) // Where **path** is defined above.

Related

AsyncImage not loading : Coil-Jetpack Compose

I have a Jetpack Compose app that makes a call to the Picsum API (endpoint: https://picsum.photos/v2/list) and tries to display them in a LazyList using the Coil library (2.1.0). I have the typical Retrofit2 + OkHttp3 setup, and am able to get all the necessary data, including the required image URL, from the endpoint. So far so good.
However, despite having valid image URLs, I am not able to display the image in the view. I've tried both AsyncImage and Image with rememberAsyncImagePainter but to no avail. I even tried hard-coding the URL string, but it still doesn't display anything. I don't know what I'm doing wrong!
Similar posts mention the INTERNET permission, but I already have it in my AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
Here is the link to my github project, and below are snippets of relevant code:
PhotoCard.kt
#Composable
fun PhotoCard(photo: Photo, ... ) {
...
Image(
painter = rememberAsyncImagePainter(photo.url),
...
)
}
PhotoListViewModel.kt
class PhotoListViewModel #Inject constructor(
private val getPhotoListUseCase: GetPhotoListUseCase
) : ViewModel() {
private val _pageState = MutableStateFlow(PageState<Photo>())
val pageState = _pageState.asStateFlow()
fun loadPhotoList(loadNextPage: Boolean = false) {
...
getPhotoListUseCase.invoke().let { response ->
if (response == null) {
...
} else {
_pageState.update {
it.copy(data = response, isInflight = false)
}
}
}
}
}
PhotoListRepository.kt
interface PhotoListRepository {
suspend fun getPhotoList(): List<Photo>?
}
PhotoListApi.kt
interface PhotoListApi {
#GET("v2/list")
suspend fun getPhotoList(): List<Photo>?
}
Photo.kt
data class Photo(
val id: String,
val author: String,
val width: Int,
val height: Int,
val url: String,
val download_url: String
)
As I see in the API url is not the direct image link. It is basically the page url of the image.
Use photo.download_url instead of photo.url

Random image from folder

How can I make possible that the app will load all of the images from the specific folder and then put in array and choose one image randomly? When chose one then pass to the fronted to show the image. How to do that too?
I am C# developer but not long time ago I found ElectronJS and this framework does everything easier so therefore I am moving to this framework.
I did in C# programming this way:
// basic settings.
var ext = new List<string> { ".jpg", ".gif", ".png" };
// we use same directory where program is.
string targetDirectory = Directory.GetCurrentDirectory() + "\\assets\\" + "images\\" + "animals\\";
// Here we create our list of files
// New list
// Use GetFiles to getfilenames
// Filter unwanted stuff away (like our program)
if (Directory.Exists(targetDirectory))
{
Files = new List<string>
(Directory.GetFiles(targetDirectory, "*.*", SearchOption.TopDirectoryOnly)
.Where(s => ext.Any(es => s.EndsWith(es))));
// Show first picture so we dont need wait 3 secs.
ChangePicture();
}
else
{
panel5.BackgroundImage = new Bitmap(Resources.doggy);
}
I don't know how to do in ElectronJS.
Thank you in advance the answers.
Alright. I found the solution.
However I don't understand the people who are giving negative reputation for the opened question. If they are giving negative reputation then they could explain why.
Well anyway, I did fix this issue with this way:
I created images.js file and added this:
var fs = require('fs');
function getRandImage() {
var files = fs.readdirSync('./assets/images/animals/')
/* now files is an Array of the name of the files in the folder and you can pick a random name inside of that array */
let chosenFile = files[Math.floor(Math.random() * files.length)]
console.log('../assets/images/animals/' + chosenFile);
return '../assets/images/animals/' + chosenFile;
}
module.exports = { getRandImage }
I used console to see if the value is correct, otherwise others can delete that part.
Sending the data to the renderer process:
const { getRandImage } = require('./images');
child.webContents.send('random-image', getRandImage());
I did put in the preload.js file the following (I used the starter pack electronjs github to start with something):
var { ipcRenderer } = require('electron');
ipcRenderer.on('random-image', function (event, store) {
document.getElementById("randompic").src = store;
console.log(store);
});
Same here, I did use console.log just for test the value is correct and I used to change the randompic ID related image src html to the randomly chosen image.
Hopefully I did helping those people who are newbie as me.

Hashset handling to avoid stuck in loop during iteration

I'm working on image mining project, and I used Hashset instead of array to avoid adding duplicate urls while gathering urls, I reached to the point of code to iterate the Hashset that contains the main urls and within the iteration I go and download the the page of the main URL and add them to the Hashet, and go on , and during iteration I should exclude every scanned url, and also exclude ( remove ) every url that end with jpg, this until the Hashet of url count reaches 0, the question is that I faced endless looping in this iteration , where I may get url ( lets call it X )
1- I scan the page of url X
2- get all urls of page X ( by applying filters )
3- Add urls to the Hashset using unioinwith
4- remove the scanned url X
the problem comes here when one of the URLs Y, when scanned bring X again
shall I use Dictionary and the key as "scanned" ?? I will try and post the result here, sorry it comes to my mind after I posted the question...
I managed to solve it for one url, but it seems it happens with other urls to generate loop, so how to handle the Hashset to avoid duplicate even after removing the links,,, I hope that my point is clear.
while (URL_Can.Count != 0)
{
tempURL = URL_Can.First();
if (tempURL.EndsWith("jpg"))
{
URL_CanToSave.Add(tempURL);
URL_Can.Remove(tempURL);
}
else
{
if (ExtractUrlsfromLink(client, tempURL, filterlink1).Contains(toAvoidLoopinLinks))
{
URL_Can.Remove(tempURL);
URL_Can.Remove(toAvoidLoopinLinks);
}
else
{
URL_Can.UnionWith(ExtractUrlsfromLink(client, tempURL, filterlink1));
URL_Can.UnionWith(ExtractUrlsfromLink(client, tempURL, filterlink2));
URL_Can.Remove(tempURL);
richTextBox2.PerformSafely(() => richTextBox2.AppendText(tempURL + "\n"));
}
}
toAvoidLoopinLinks = tempURL;
}
Thanks for All, I managed to solve this issue using Dictionary instead of Hashset, and use the Key to hold the URL , and the value to hold int , to be 1 if the urls is scanned , or 0 if the url still not processed, below is my code.
I used another Dictionary "URL_CANtoSave to hold the url that ends with jpg "my target"...and this loop of While..can loop until all the url of the website ran out based on the values you specify in the filter string variable that you parse the urls accordingly.
so to break the loop you can specify amount of images url to get in the URL_CantoSave.
return Task.Factory.StartNew(() =>
{
try
{
string tempURL;
int i = 0;
// I used to set the value of Dictionary Key, 1 or 0 ( 1 means scanned,
0 means not yet and to iterate until all the Dictionry Keys are scanned or you break in the middle based on how much images urls you collected in the other Dictionary
while (URL_Can.Values.Where(value => value.Equals(0)).Any())
{
// take 1 key and put it in temp variable
tempURL = URL_Can.ElementAt(i).Key;
// check if it ends with your target file extension. in this case image file
if (tempURL.EndsWith("jpg"))
{
URL_CanToSave.Add(tempURL,0);
URL_Can.Remove(tempURL);
}
// if not image go and download the page based on the url and keep analyzing
else
{
// if the url not scanned before then
if (URL_Can[tempURL] != 1)
{
// here it seems complex little bit, where Add2Dic is process to add to Dictionaries without adding the Key again ( solving main problem !! )
"ExtractURLfromLink" is another process that return dictionary with all links analyzed by downloading the document string of the url and analyzing it ,
you can add remove filter string based on you analysis
Add2Dic(ExtractUrlsfromLink(client, tempURL, filterlink1), URL_Can, false);
Add2Dic(ExtractUrlsfromLink(client, tempURL, filterlink2), URL_Can, false);
URL_Can[tempURL] = 1; // to set it as scanned link
richTextBox2.PerformSafely(() => richTextBox2.AppendText(tempURL + "\n"));
}
}
statusStrip1.PerformSafely(() => toolStripProgressBar1.PerformStep());
// here comes the other trick to keep this iteration keeps going until it scans all gathered links
i++; if (i >= URL_Can.Count) { i = 0; }
if (URL_CanToSave.Count >= 150) { break; }
}
richTextBox2.PerformSafely(() => richTextBox2.Clear());
textBox1.PerformSafely(() => textBox1.Text = URL_Can.Count.ToString());
return ProcessCompleted = true;
}
catch (Exception aih)
{
MessageBox.Show(aih.Message);
return ProcessCompleted = false;
throw;
}
{
richTextBox2.PerformSafely(()=>richTextBox2.AppendText(url+"\n"));
}
})

Can I load custom jsm modules in bootstrap.js of a restartless add-on?

I'm trying to load a custom module in a restartless add-on, using the following:
chrome/content/modules/Test.jsm:
var EXPORTED_SYMBOLS = [ 'Test' ];
let Test = {};
chrome.manifest:
content test chrome/content/
bootstrap.js:
const Cu = Components.utils;
// Tried this first, but figured perhaps chrome directives aren't loaded here yet
// let test = Cu.import( 'chrome://test/modules/Test.jsm', {} ).Test;
function install() {
let test = Cu.import( 'chrome://test/modules/Test.jsm', {} ).Test;
}
function uninstall() {
let test = Cu.import( 'chrome://test/modules/Test.jsm', {} ).Test;
}
function startup() {
let test = Cu.import( 'chrome://test/modules/Test.jsm', {} ).Test;
}
function shutdown() {
let test = Cu.import( 'chrome://test/modules/Test.jsm', {} ).Test;
}
However, I get the following types of WARN messages (this one was for shutdown(), but basically identical for all functions and in the earlier attempt in the global scope):
1409229174591 addons.xpi WARN Exception running bootstrap method
shutdown on test#extensions.codifier.nl: [Exception... "Component
returned failure code: 0x80070057 (NS_ERROR_ILLEGAL_VALUE)
[nsIXPCComponents_Utils.import]" nsresult: "0x80070057
(NS_ERROR_ILLEGAL_VALUE)" location: "JS frame ::
resource://gre/modules/addons/XPIProvider.jsm ->
file:///test/bootstrap.js :: shutdown :: line 21" data: no] Stack
trace: shutdown()#resource://gre/modules/addons/XPIProvider.jsm ->
file:///test/bootstrap.js:21 <
XPI_callBootstrapMethod()#resource://gre/modules/addons/XPIProvider.jsm:4232
<
XPI_updateAddonDisabledState()#resource://gre/modules/addons/XPIProvider.jsm:4347
<
AddonWrapper_userDisabledSetter()#resource://gre/modules/addons/XPIProvider.jsm:6647
< uninstall()#extensions.xml:1541 < oncommand()#about:addons:1 <
Are chrome.manifest directives not yet available in bootstrap.js? Or is what I am attempting some kind of security violation, perhaps? Or am I simply doing something trivially wrong?
What I was hoping to achieve, is that I could do something like the following:
chrome/content/modules/Test.jsm:
var EXPORTED_SYMBOLS = [ 'Test' ];
let Test = {
install: function( data, reason ) {
},
/* etc */
bootstrap: function( context ) {
context.install = this.install;
context.uninstall = this.uninstall;
context.startup = this.startup;
context.shutdown = this.shutdown;
}
}
bootstrap.js:
const Cu = Components.utils;
Cu.import( 'chrome://test/modules/Test.jsm' );
Test.bootstrap( this );
Perhaps it's a bit over the top to begin with, but I just kind of like the idea of hiding implementations in modules and/or objects and keeping bootstrap.js super clean.
If you happen to have suggestions on how to achieve this by other means: I'm all ears.
Yes you can your path is wrong though.
Just do this:
let test = Cu.import( 'chrome://test/content/modules/Test.jsm', {} ).Test;
notice the /content/
You don't have to do the .Test unless you want the lower case test to hold it. You can just do:
Cu.import( 'chrome://test/content/modules/Test.jsm');
and use as Test.blah where blah is whatever is in the JSM module.
This code can go anywhere, it does not have to be in the install function.
Make sure to unload the custom JSM modules or else it can lead to zombie compartments which is bad for memory. Read here:
last paragraph here: https://developer.mozilla.org/en-US/docs/Extensions/Common_causes_of_memory_leaks_in_extensions
more reading but optional: https://developer.mozilla.org/en-US/docs/Zombie_compartments
Beyond #Noitidart's answer, you don't have to use chrome.manifest' and register a content package if your only concern is how to import your module.
function install(data, reason) {
Components.utils.import(data.resourceURI.spec + "relative/path/to/your/module.jsm");
}

Security Error when trying to load content from resource in a Firefox Addon (SDK)

I am creating a firefox addon using the SDK. My goal is simple, to intercept a specific iframe and load my own HTML page (packaged as a resource with my addon) instead of the content that was requested originally.
So far I have the following code:
var httpRequestObserver =
{
observe: function(subject, topic, data)
{
var httpChannel, requestURL;
if (topic == "http-on-modify-request") {
httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
requestURL = httpChannel.URI.spec;
var newRequestURL, i;
if (/someurl/.test(requestURL)) {
var ioService = Cc["#mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
httpChannel.redirectTo(ioService.newURI(self.data.url('pages/test.html'), undefined, undefined));
}
return;
}
}
};
var observerService = Cc["#mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
observerService.addObserver(httpRequestObserver, "http-on-modify-request", false);
This code works in that it detects the proper iframe loading and does the redirect correctly. However, I get the following error:
Security Error: Content at http://url.com may not load or link to
jar:file:///.../pages/test.html.
How can I get around this limitation?
actually man i was really over thinking this.
its already solved when I changed to using loadContext. Now when you get loadContext you get the contentWindow of whatever browser element (tab browser, or frame or iframe) and then just abort the http request like you are doing and then loadContext.associatedWindow.document.location = self.data('pages/tests.html');
done
ill paste the code here removing all the private stuff. you might need the chrome.manifest ill test it out and paste the code back here
Cu.import('resource://gre/modules/Services.jsm');
var httpRequestObserver = {
observe: function (subject, topic, data) {
var httpChannel, requestURL;
if (topic == "http-on-modify-request") {
httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
requestURL = httpChannel.URI.spec;
var newRequestURL, i;
if (/someurl/.test(requestURL)) {
var goodies = loadContextGoodies(httpChannel);
if (goodies) {
httpChannel.cancel(Cr.NS_BINDING_ABORTED);
goodies.contentWindow.location = self.data.url('pages/test.html');
} else {
//dont do anything as there is no contentWindow associated with the httpChannel, liekly a google ad is loading or some ajax call or something, so this is not an error
}
}
return;
}
}
};
Services.obs.addObserver(httpRequestObserver, "http-on-modify-request", false);
//this function gets the contentWindow and other good stuff from loadContext of httpChannel
function loadContextGoodies(httpChannel) {
//httpChannel must be the subject of http-on-modify-request QI'ed to nsiHTTPChannel as is done on line 8 "httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);"
//start loadContext stuff
var loadContext;
try {
var interfaceRequestor = httpChannel.notificationCallbacks.QueryInterface(Ci.nsIInterfaceRequestor);
//var DOMWindow = interfaceRequestor.getInterface(Components.interfaces.nsIDOMWindow); //not to be done anymore because: https://developer.mozilla.org/en-US/docs/Updating_extensions_for_Firefox_3.5#Getting_a_load_context_from_a_request //instead do the loadContext stuff below
try {
loadContext = interfaceRequestor.getInterface(Ci.nsILoadContext);
} catch (ex) {
try {
loadContext = subject.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
} catch (ex2) {}
}
} catch (ex0) {}
if (!loadContext) {
//no load context so dont do anything although you can run this, which is your old code
//this probably means that its loading an ajax call or like a google ad thing
return null;
} else {
var contentWindow = loadContext.associatedWindow;
if (!contentWindow) {
//this channel does not have a window, its probably loading a resource
//this probably means that its loading an ajax call or like a google ad thing
return null;
} else {
var aDOMWindow = contentWindow.top.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
var gBrowser = aDOMWindow.gBrowser;
var aTab = gBrowser._getTabForContentWindow(contentWindow.top); //this is the clickable tab xul element, the one found in the tab strip of the firefox window, aTab.linkedBrowser is same as browser var above //can stylize tab like aTab.style.backgroundColor = 'blue'; //can stylize the tab like aTab.style.fontColor = 'red';
var browser = aTab.linkedBrowser; //this is the browser within the tab //this is where the example in the previous section ends
return {
aDOMWindow: aDOMWindow,
gBrowser: gBrowser,
aTab: aTab,
browser: browser,
contentWindow: contentWindow
};
}
}
//end loadContext stuff
}
NOTE: Now try this first, I didn't test it yet, if you get a security error when it tries to redirect then create a chrome.manifest file and put it in the root directory. If it throws a security error than you definitely need a chrome.manifest file and that will without question fix it up. I'll test this myself later tonight when I get some time.
The chrome.manifest should look like this:
content kaboom-data ./resources/kaboom/data/ contentaccessible=yes
Then in the code way above change the redirect line from goodies.contentWindow.location = self.data.url('pages/test.html'); to goodies.contentWindow.location = 'chrome://kaboom-data/pages/test.html');.
see this addon here: https://addons.mozilla.org/en-US/firefox/addon/ghforkable/?src=search
in the chrome.manifest file we set the contentaccessible parameter to yes
you dont need sdk for this addon. its so simple, just ocpy paste that into a bootstrap skeleton as seen here:
Bootstrap With Some Features, Like chrome.manifest which you will need
Bootstrap Ultra Basic
if you want to really do a redirect of a page to your site, maybe you want to make a custom about page? if you would like ill throw togather a demo for you on making a custom about page. you can see a bit hard to understand demo here
posting my trials here so it can help all:
trail 1 failed - created chrome.manifest file with contents content kaboom-data resources/kaboom/data/ contentaccessible=yes
var myuri = Services.io.newURI('chrome://kaboom-data/content/pages/test.html', undefined, undefined);
httpChannel.redirectTo(myuri);
Error Thrown
Security Error: Content at http://digg.com/tools/diggthis/confirm? may
not load or link to
jar:file:///C:/Documents%20and%20Settings/SONY%20VAIO/Application%20Data/Mozilla/Firefox/Profiles/vr10qb8s.default/extensions/jid1-g4RtC8vdvPagpQ#jetpack.xpi!/resources/kaboom/data/pages/test.html.
trial 2 failed - created resource in bootstrap.js
alias.spec =
file:///C:/Documents%20and%20Settings/SONY%20VAIO/Application%20Data/Mozilla/Firefox/Profiles/vr10qb8s.default/extensions/jid1-g4RtC8vdvPagpQ#jetpack.xpi
alias updated to spec:
jar:file:///C:/Documents%20and%20Settings/SONY%20VAIO/Application%20Data/Mozilla/Firefox/Profiles/vr10qb8s.default/extensions/jid1-g4RtC8vdvPagpQ#jetpack.xpi!/
let resource = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
let alias = Services.io.newFileURI(data.installPath);
Cu.reportError('alias.spec = ' + alias.spec);
if (!data.installPath.isDirectory()) {
alias = Services.io.newURI("jar:" + alias.spec + "!/", null, null);
Cu.reportError('alias updated to spec: ' + alias.spec);
}
resource.setSubstitution("kaboom_data", alias);
...
var myuri = Services.io.newURI('resource://kaboom_data/resources/kaboom/data/pages/test.html', undefined, undefined);
httpChannel.redirectTo(myuri);
Error Thrown
Security Error: Content at http://digg.com/tools/diggthis/confirm? may
not load or link to
jar:file:///C:/Documents%20and%20Settings/SONY%20VAIO/Application%20Data/Mozilla/Firefox/Profiles/vr10qb8s.default/extensions/jid1-g4RtC8vdvPagpQ#jetpack.xpi!/resources/kaboom/data/pages/test.html.
CONCLUSION
in both trials above it was the weirdest thing, it wouldnt show the resource or chrome path in the security error thrown but it would give the full jar path. Leading me to believe that this has something to do with redirectTo function.
The solution that did work was your solution of
var gBrowser = utils.getMostRecentBrowserWindow().gBrowser;
var domWin = httpChannel.notificationCallbacks.getInterface(Ci.nsIDOMWindow);
var browser = gBrowser.getBrowserForDocument(domWin.document);
//redirect
browser.loadURI(self.data.url('pages/test.html'));
however I changed this to use loadContext instead of this method because it is the recommended way. also gBrowser to getMostRecentBrowserWindow will fail if the url load is slow and in that time the user swithces to another tab or window
I also changed to use Services.jsm as you had imported Cu anyways. Using Services.jsm is super fast not even blink fast. Its just a pointer.
Im still working on trying to the redirectTo method working its really bothering me. The changes I made are to my local copy.
Have you considered turning your local HTML file into a data URL and loading that?

Resources