Having trouble Viewing/Adding Attachments

5440
9
Jump to solution
11-16-2015 07:05 AM
by Anonymous User
Not applicable

This is my first attempt at making an App with the ArcGIS Runtime SDK for Qt and App Studio.  I've been loosely following QML code examples found in the ArcGIS Runtime team's sample app and Lucas Danzinger's repo on GitHub for the San Diego Brewery Explorer.  The first problem I'm having is that I cannot return the attachments of a GeodatabaseFeature using the attachmentModel property.

In the screenshot below I am doing an identify against a feature service point (OID 1060) that has one attachment.  The identify is working but I cannot seem to fill the combobox with the list of attachment images:

identifyError.png

Another weird thing that happens here is the Latitude and Longitude (both double) values are showing up as undefined even when valid coordinates are added to the table and using the toString() method on them while building the list of fields throws an error.  However, I am more concerned about getting the attachments to show up.  The entire project code is attached.  But from the screenshot, you can see the console log is reporting that the selectedFeature is valid as it logs the resulting JSON, but for some reason the attachment count is coming back empty.

The second problem I am having is with adding attachments.  Below is my form.  It lets me select a file for attachment (currently only testing on Windows) and on the onAccepted() signal, the console logs the name, size and url of attachment (this comes back undefined which makes sense) by using:

console.log("GDB Attachment: " + geodatabaseAttachment.name + ", " + geodatabaseAttachment.size + ", " + geodatabaseAttachment.url);

formAttachment.PNG

And here I am getting an error that there is a problem adding the attachment.  On the onApplyAttachmentEditsStatusChanged() signal I am also not able to pull the errors as I get an empty JSON object.  I am probably doing some thing wrong there too:

attachmentError.PNG

In all the code samples I've seen, a new GeodatabaseAttachment is created like this:

var geodatabaseAttachment = ArcGISRuntime.createObject("GeodatabaseAttachment");

However, for some reason I am unable to import the ArcGIS.Runtime 10.26 module and I get this error:

file:///C:/Users/calebma/ArcGIS/AppStudio/Apps/f2fed33a81d74109b4ec4a8b03eec5d1/Empty.qml:17:1: plugin cannot be loaded for module "ArcGIS.Runtime": Cannot load library C:/Users/calebma/Applications/ArcGIS/AppStudio/bin/qml/ArcGIS/Runtime.10.26/ArcGISRuntimePlugin.dll: The specified module could not be found.

     import ArcGIS.Runtime 10.26

     ^

In the attached project, the "Empty.qml" is the main file and the "EditWindow.qml" is the source code for the edit form.  By default when you add a new sighting it tries to grab your device position, if not you can manually add a point.  For testing, you can uncomment line 82 to disable using the device location.  Also, this was built using App Studio with the "Toolbar and single content area" layout template if that helps.  If anyone could help me out I would greatly appreciate it!

Also, if anyone has any good code examples of how to add a legend with the ability to toggle layers on/off that would be a bonus!  Thanks.

0 Kudos
1 Solution

Accepted Solutions
LucasDanzinger
Esri Frequent Contributor

Caleb-

I'm picturing how the app would work. Are you allowing the user to select potentially several attachments to add to a newly created feature all in the form? If so, are you storing the list of attachments in some other list, and then once your user submits the form, you try to loop through the list and add all of those attachments to the feature all at once? If so, I wonder if a better workflow would be to call addAttachment each time your user selects their attachment. Then once they click submit on the form, you only are really calling apply edits. The issue right now is that you end up in a bit of a race condition because the addattachment finishes for 1, then calls apply edits, then in the mean time, the add attachment finishes for another and tries to call apply edits, but the first apply edits isn't done yet, so it gets rejected. The simplest way around this is to change when/how you are calling addAttachment so it isn't in a loop. If you for some reason need to use this approach, you will likely need to code up some JavaScript that'll make sure to not call apply edits until all of the attachments in the list are successfully added, then call apply edits once all have been successfully added.

Thanks,

Luke

View solution in original post

9 Replies
LucasDanzinger
Esri Frequent Contributor

For the first issue, is the combo box always filling it with the right number of attachments, only it doesn't display the name? You might need to set up a list view where the delegate accesses the "name" role from the model.

For the adding issue, do you get an error when actually adding it, or when you call apply edits?

For your importing issue, you will not actually import ArcGIS.Runtime 10.26 if you are using AppStudio. AppStudio bundles the Runtime API into its AppFramework plugin. So the code samples you see from the Runtime SDK can be applied to AppStudio projects, but the import statement will be a bit different.

For toggling layers, a QML Checkbox works nicely. You could create a Column full of checkboxes, and then bind the checked property of the Checkbox to the layer's visible property.

-Luke

by Anonymous User
Not applicable

Thanks for the suggestions, I have made some progress.  I found my problem with adding attachments, it was because I was not waiting for the proper signals (onAddAttachmentStatusChanged and onApplyAttachmentEditsStatusChanged).  Once I waited for these I am able to add attachments.  However, I cannot add more than one to a feature.  It does not seem to have time to add any after the first one.  This is the code I'm using:

GeodatabaseFeatureServiceTable {
    id: sightingFST
    url: "http://services.arcgis.com/g2UdLvZr5lOrmlXl/arcgis/rest/services/EAB_FS/FeatureServer/0"

    onApplyFeatureEditsStatusChanged: {
        if (applyFeatureEditsStatus === Enums.ApplyEditsStatusCompleted) {
            msgDialog.showMsgDialog("Success!", "Your EAB sighting has been successfully reported!");
            
            // addedId is the OBJECTID of the feature that was just added
            if (addedId >= 0) {
                console.log("Adding Id attachment for: " + addedId);

                // add all attachments
                console.log(JSON.stringify(addedAttachmentsList));
                for (var i = 0; i < addedAttachmentsList.length; i++) {
                    var attachment = addedAttachmentsList;
                    console.log("Geodatabase Attachment: " + attachment.name);
                    addAttachment(addedId.toString(), attachment);
                    attachmentsBusy = true; //is true while trying to add an attachment
                    while (attachmentsBusy === true) {
                        System.wait(1000); //make calls every second to see if it is done yet
                        console.log("Adding attachment " + '"' + attachment.name + '"...');
                    }
                }
            }
        }
    }

   onAddAttachmentStatusChanged: {

        console.log("attachment status: " + addAttachmentStatus)
        if (addAttachmentStatus === Enums.AttachmentEditStatusCompleted) {
            applyAttachmentEdits();
            attachmentsBusy = false;
        }

        else if (addAttachmentStatus === Enums.AttachmentEditStatusErrored) {
            console.log("Errors: " + AttachmentEditResult.error) // how do I get errors?
        }
    }

    onApplyAttachmentEditsStatusChanged: {
        if (applyAttachmentEditsStatus == Enums.AttachmentEditStatusErrored) {
            console.log("Failed to add attachment!");
            console.log(JSON.stringify(applyAttachmentEditsErrors));
        }
        else if (applyAttachmentEditsStatus == Enums.AttachmentEditStatusCompleted) {
            console.log("Successfully added attachment");
        }

        if (!attachmentsBusy); {
            addedId = -1;
            addedAttachmentsList.length -= 1 //remove attachment on each iteration
        }
    }
    
    // this is used to grab attachments for identify operations
    onQueryAttachmentInfosStatusChanged: {
        if (queryAttachmentInfosStatus === Enums.QueryAttachmentInfosStatusCompleted){
            identifyDialog.visible = true;
            console.log("Attachment Infos: " + JSON.stringify(attachmentInfos))
            for (var i=0; i < attachmentInfos.length; i++) {
                var attInfo = attachmentInfos;
                idAttachments.push(attInfo.name);
            }
        }

        else if(queryAttachmentInfosStatus === Enums.QueryAttachmentInfosStatusErrored) {
            console.log("Failed to grab attachments");
            console.log("Attachment Query Errors: " + JSON.stringify(queryAttachmentInfosError));
        }

    }
 }

And here is what the console says:

qml: Adding Id attachment for: 1120

qml: [{"objectName":"","objectType":"GeodatabaseAttachment","name":"pin_star_red_d.png","contentType":"image/png","size":1986,"isDataLocal":true,"attachmentId":"0","attachmentUrl":{}},{"objectName":"","objectType":"GeodatabaseAttachment","name":"ic_menu_editmap_light_d.png","contentType":"image/png","size":1450,"isDataLocal":true,"attachmentId":"0","attachmentUrl":{}}]

qml: Geodatabase Attachment: pin_star_red_d.png

qml: attachment status: 2

qml: Adding attachment "pin_star_red_d.png"...

qml: Geodatabase Attachment: ic_menu_editmap_light_d.png

qml: attachment status: 2

qml: Adding attachment "ic_menu_editmap_light_d.png"...

qml: Successfully added attachment

And when I do an identify on the newly added point, the second attachment is queried but it's ID is negative which seems to indicate it wasn't pushed to the database.  Here's what the console says:

qml: Attachment Infos: [{"attachmentId":22,"contentType":"image/png","name":"pin_star_red_d.png","size":1986},{"attachmentId":-11,"contentType":"image/png","name":"ic_menu_editmap_light_d.png","size":1450}]

And yes, about the Runtime import...I was a little confused about the differences between App Studio and just using the SDK by itself. And I saw that you had a good example of doing adding a legend in your charting sample on GitHub.  I think I can figure those things out on my own.  Do you have any suggestions/code samples of adding more than one attachment to a feature service after a new feature is added?  Thanks!

0 Kudos
LucasDanzinger
Esri Frequent Contributor

Caleb-

I'm picturing how the app would work. Are you allowing the user to select potentially several attachments to add to a newly created feature all in the form? If so, are you storing the list of attachments in some other list, and then once your user submits the form, you try to loop through the list and add all of those attachments to the feature all at once? If so, I wonder if a better workflow would be to call addAttachment each time your user selects their attachment. Then once they click submit on the form, you only are really calling apply edits. The issue right now is that you end up in a bit of a race condition because the addattachment finishes for 1, then calls apply edits, then in the mean time, the add attachment finishes for another and tries to call apply edits, but the first apply edits isn't done yet, so it gets rejected. The simplest way around this is to change when/how you are calling addAttachment so it isn't in a loop. If you for some reason need to use this approach, you will likely need to code up some JavaScript that'll make sure to not call apply edits until all of the attachments in the list are successfully added, then call apply edits once all have been successfully added.

Thanks,

Luke

by Anonymous User
Not applicable

Luke,

That is a great idea!  Yes, I am definitely not succeeding in the looping method I'm trying right now.  The only problem I see is that When the form is up, the feature is not added until the user hits the "Send Report" button (not enabled til the user adds an attachment) shown in screenshots from the OP.  Therefore, There is no OBJECTID yet to add an attachment to.

However, this has got me thinking...The addAttachment() method does return an OID.  In my code, the current location is grabbed from the device right before the Attribute Form pops up.  So what I'm thinking  I could do is make a call to addFeature() with JUST the geometry and then call applyFeatureEdits() (once successful, open the form window).  Now, in the meantime, the user is filling out the form.  So what you're saying is each time an attachment is loaded, I could call addAttachment(), which raises another question.  Can I call addAttachment() multiple times and then push them all to the database at once using the applyAttachmentEdits(), or do I need to flush this out each time an attachment is added?

If I can push them all at once, I could have the "Send Report" button disabled until the applyAttachmentEditsStatus() signal fires with a completed status (and the Send Report button now is enabled), and then when the user hits the submit form, I can call updateFeature() and applyFeatureEdits() again to bring in the attributes the user selected?  Is that the kind of workflow you are imagining or is there a better way?

0 Kudos
LucasDanzinger
Esri Frequent Contributor

Yea that could work. You could also maybe have some global int that keeps track of the amount of attachments you are going to add. Then, your addAttachment signal handler won't call apply until it matches the total amount.

by Anonymous User
Not applicable

Hi Caleb

I had the same issue and got past it using a method kind of along the lines of what Luke mentioned. Rather than a straight 'for loop' that attempts to pause until ready to do the next one (which I tried but couldn't get to work!), I created an array, and as the user added attachments I pushed the attachment objects into this array (without actually attaching them at this stage). I then had a count property, set initially to -1. The count property had an onCountChanged handler along the lines of:

    property var geodatabaseAttachmentsToAdd: []

    property int addImagesCount: -1;

    onAddImagesCountChanged: {

        if(addImagesCount > -1){

            var geodatabaseAttachment = geodatabaseAttachmentsToAdd[addImagesCount];

            if(geodatabaseAttachment){

                featureLayer.featureTable.addAttachment(newObjectId, geodatabaseAttachment);

            }

        }

    }

The workflow was then: user selects images and "attaches" them and fills out other attributes, clicks submit, feature gets created, new objectId gets created, then addImagesCount property is set to 0. This triggers it to actually add the first attachment to the feature.

Once the onAddAttachmentStatusChanged signals the attachment is added, it increments the counter until no more are left to add. At which point you would call the applyAttachmentEdits. e.g.

if(addImagesCount + 1 < geodatabaseAttachmentsToAdd.length){  addImagesCount++} else {addImagesCount = -1}

It's just an alternate way to loop through them. It seems to work most of the time although i've had my app report errors adding sometimes so I still obviously don't have it working 100%. So am interested in comments/suggestions from anyone else on this.

Cheers,

-Paul

by Anonymous User
Not applicable

Paul,

Thanks, this is very useful and kind of similar to what I was trying to do (except done way more efficiently ).  I am going to give this another try tonight based on Luke's and your suggestions and I will be sure to post a working solution if I can figure it out.

One other question...So are the addFeature() and addAttachment() methods similar to the ArcGIS JavaScript API where once these are called the edits are pushed right to the service?  After looking at some code samples it looked like calling appyFeatureEdits() needed to be called after the addFeature() and applyAttachmentEdits() gets called after AddAttachment().  Is this correct, or doe these methods only need to be called when existing features are edited? 

The documentation specifies that the addFeature() and addAttachment() go to the table, and the apply edits pushes them to the server, so does this mean they do not actually show up in the feature service until applying the edits?

0 Kudos
GarethWalters3
New Contributor III

Hi Paul,

Great note. I took just your approach and got it working.

One difference I have is that my submit button has a custom signal that I have "connected" to the addAttachmentStatusChanged event handler.

I did it this way because I have wrapped all the geodatabase components in one qml file. and don't have direct access to them.

       Button {
            id: submitButton
            text: "Submit"


            enabled: idText.text > ""
          
            //Custom signal to connect to Geodabase attachment event handler


            signal addFileIndex()


            onAddFileIndex: {
                if  (gdbComponents.gdbInspectionsFeatureTable.addAttachmentStatus === Enums.AttachmentEditStatusCompleted){
                    addImagesCount++;
                }


                console.log("from custom signal", gdbComponents.gdbInspectionsFeatureTable.addAttachmentStatus);
            }


            property int inspectionID: -1


            property int addImagesCount: -1
            onAddImagesCountChanged: {
                if (addImagesCount > -1){
                    var filesArray = fileFolder.fileNames();
                    var geodatabaseAttachment = filesArray[addImagesCount];
                    if(geodatabaseAttachment){
                        var gdbAttachment = ArcGISRuntime.createObject("GeodatabaseAttachment");
                        var photoString = fileFolder.path + "/" + geodatabaseAttachment;
                        console.log("about to add:", photoString);;


                        gdbAttachment.loadFromFile(photoString, "image/jpeg");


                        gdbComponents.gdbInspectionsFeatureTable.addAttachment(inspectionID, gdbAttachment);
                    }
                }
            }


            onClicked: {
                var date = new Date();
                date = date.getTime();


                //------------ GDB FEATURE ------------------
                jsonFeature.attributes.created_date = date;
                jsonFeature.attributes.last_edited_date = date;


                console.log(JSON.stringify(jsonFeature, undefined, 2));


                var feature = ArcGISRuntime.createObject("GeodatabaseFeature");
                feature.json = jsonFeature;


                //Add the new feature to the GDB
                var id = gdbComponents.gdbFeatureTable.addFeature(feature);


                console.log("feature id", id);




                //------------ RELATED TABLE ------------------
                //Get the Global ID to link the related table
                jsonRelatedInspection.attributes.IntersectionGUID =gdbComponents.gdbFeatureTable.feature(id).attributes.GlobalID;
                jsonRelatedInspection.attributes.created_date = date;
                jsonRelatedInspection.attributes.last_edited_date = date;


                console.log(JSON.stringify(jsonRelatedInspection, undefined, 2));


                console.log("number of features", gdbComponents.gdbFeatureTable.numberOfFeatures);


                var inspectionFeature = ArcGISRuntime.createObject("GeodatabaseFeature");
                inspectionFeature.json = jsonRelatedInspection;
                inspectionID = gdbComponents.gdbInspectionsFeatureTable.addFeature(inspectionFeature);


                console.log("inspection id:", inspectionID, gdbComponents.gdbInspectionsFeatureTable.feature(inspectionID).attributes.GlobalID);


                if(inspectionID > -1){
                    console.log("lets add some attachments...")
                    addImagesCount = 0;
                }


                //------------ FORM CLEANUP ------------------
                forms_Section.state = "";


            }


            Component.onCompleted: {
                gdbComponents.gdbInspectionsFeatureTable.addAttachmentStatusChanged.connect(addFileIndex)
            }
        }
by Anonymous User
Not applicable

Luke and Paul,

Thanks for the help!  I was able to add multiple attachments by following some of the suggestions that Luke made.  Here are the steps I took:

  1. Get the current device location point, and add a new feature with this point and set default attributes.
  2. called applyFeatureEdits() to push the feature to the service and then display the attribute edit form
  3. Each time the user adds an attachment, the addAttachment() method is called from the form.  While the attachment is being uploaded there is a global property called attachmentsBusy that is true until the addAttachmentsEditsStatus is complete.  the Send Report button is  not enabled until the attachmentsBusy property is false. When the user hits the Send Report button, it calls applyAttachmentsEdits() and closes the edit form window.
  4. Upon an addAttachhmentsEdits completed signal, the attributes from the form are pushed back to the feature using updateFeature() and applyFeatureEdits() is called again.

I'm still having some issues with viewing attachments, but I think I can figure this out on my own.  I may end up posting a separate question for that if I cannot figure it out.