Esri / offline-editor-js

ArcGIS JavaScript library for handling offline editing and tiling.
http://esri.github.io/offline-editor-js/demo/
Apache License 2.0
159 stars 142 forks source link

error in offline-tpk-src.js when app is built with Phonegap #319

Closed noeltelosa closed 9 years ago

noeltelosa commented 9 years ago

Hi,

I've been having trouble working on this, I am developing a mobile application that will load a tpk file from the sdcard of the device and display the map, The application is working fine when it is running on mobile browser i.e (google chrome and firefox), however when I build the application using Phonegap so the app can be installed on the mobile device the map is not displaying anymore and got this error.

I am using Samsung Note 10.1 2014 Edition(Android Version 4.4) and a generic Android Phone(Android Version 4.1.1)

error

andygup commented 9 years ago

Hi Noel, can you step thru the code in the _unzipTileFiles() method and provide more details? I don't have any way to repro this correctly before our call. Also, all my devices are LG phones running Android 5.x. I suggest starting out by setting a breakpoint at line 381. It may just be something simple that has a workaround.

Please note as mentioned under Supported Browsers for this repo we currently only support the Chrome browser and have only done very limited proof-of-concept testing using WebView.

andygup commented 9 years ago

@noeltelosa Any luck with this?

andygup commented 9 years ago

@noeltelosa if you do get more info and this is still an issue feel free to reopen.

jaarons47 commented 9 years ago

Andy, I am having the same problem. Was there any definitive resolution on this?

Aaron Dick Mobile GIS Lead OR/WA BLM and USFS R6 aaron_dick@blm.gov

andygup commented 9 years ago

Nope, I never heard back.

I recommend starting out with the index_basicmap.html sample in the quickstart-map-phonegap repo and seeing if you can get just the map working. After that you can use it as a foundation to start adding the offline functionality. Those samples are specifically built to work in the PhoneGap/Cordova environment and follow a strict life-cycle pattern.

I'm in the process of updating those quickstart samples to Cordova 5. There's a handful of extra Cordova event handlers in there that aren't necessary anymore, but the code should work.

For production use I also strongly recommend hosting a local copy of the ArcGIS API for JavaScript using the Web Optimizer tool. This sample demonstrates how to do that. Line 70 is where the local copy of the ArcGIS JS API library is imported.

jaarons47 commented 9 years ago

Hey Andy, Yes I did already do as you suggested by using the cordova sample with indes_basicmap.html. I think the issue is the database method being used. Kind of a red flag when it returns a size of 0.0 MB and 0 tiles. It appears that IndexedDB is not supported with Cordova on Android devices (which is what I am testing with).

http://docs.phonegap.com/en/4.0.0/cordova_storage_storage.md.html.

It appears that only BlackBerry and Windows Phone support IndexedDB. It looks like WebSQL is supported. You think it is possible to change the database being used to WebSQL?

andygup commented 9 years ago

Can you confirm which version of Cordova and which version of Android you are using? I noticed in the Cordova docs URL above that it's for 4.0.0. The current shipping version is 5.1.1.

Part of the issue with older versions of Cordova is Android didn't get IndexedDB support until Android Browser 4.4 (API Level 19).

Have you tried using IndexedDB shim? That will allow the library to work on browsers that only have WebSQL. It's part of the repo and all the samples in the /samples directory include the shim.

For some reason the current Cordova docs still imply that IndexedDB is not supported in the latest Cordova version. And, it may be the case that a few Android devices running the latest OS do not support it. However, it is officially supported in Android 4.4+. For example, I have successfully tested IndexedDB using Cordova 5.1.1 compiled to Android 5.0.1 and run on a Nexus 5. I did compile directly in Android Studio rather than using the Cordova CLI.

Here's a quote from Google developer docs "For the most part, features that work in Chrome for Android should work in the new WebView.".

Last item, here's a must read doc on Android WebView, what it is and where it's headed.

jaarons47 commented 9 years ago

I am using version 5.1.1 of Cordova and Minimum Android API is 22 (just created the project a few days ago, so will set minimum back to 19 if that is the minimum that supports IndexedDB). I am testing on a Samsung S4 running Lollipop. I think that is where I am confused. Wondering how to reference IndexedDBShim.min.js in code. Was trying to do it this way... In index_basicmap.html added in...

In this situation I am calling offline-tpk-src via this code in index_basicmap.html...

tpkLayer = new O.esri.TPK.TPKLayer(); tpkLayer.on("progress", function (evt) {

When I hit this code it errors reader.addEventListener("loadend", function (evt) { "Uncaught TypeError: reader.addEventListener is not a function", source: ./dist/offline-tpk-src.js (380)

I tried adjusting for this by changing the code to... reader.onloadend = function(evt) { however it appears this onloadend event never gets hit...

I think this is probably the root of my issue with this listener not firing correctly.

One final thing I am wondering about is where is the variable "indexedDB" actually defined?
In offline-tpk-src.js I do not see where this gets defined. I tried redefining this way...

var indexedDB = window.shimIndexedDB; and then firing off var request = indexedDB.open(DB_NAME, 4);

This did not seem to help the cause at all. Sorry for all the questions and confusion on my part. Any thoughts greatly appreciated.

jaarons47 commented 9 years ago

OK so one more tidbit. I was wrong this event does get hit for reader.onloadend. It is this line that never gets hit... if(this.token != undefined){ so I guess the token is never getting generated...

                                                          reader.onloadend = function(evt) {
                                                           console.log("Made it hear!");
                                                          if(this.token != undefined){
                                                              console.log("Made it here2!");
                                                                 that._inMemTilesIndex.push("blank");
                                                                  var name = files[this.token].filename.toLocaleUpperCase();
                                                                 that._inMemTilesObject[name]= this.result;
                                                                  var size = that.ObjectSize(that._inMemTilesObject);
                                                                 if(size > 0){
                                                                console.log("Made it here3!");
                                                                    callback(deferred,data.token);
                                                                }
                                                              }
                            };
andygup commented 9 years ago

Hi, all the /samples for this repo use IndexedDBShim.

There are several options for getting access to IndexedDBShim. Option 1 is use the Setup Instructions in this repo's README it will install a local copy of IndexedDBShim, or you can clone or download IndexedDBShim yourself from github and host it locally.

Wondering how to reference IndexedDBShim.min.js in code.

IndexedDBShim is a JavaScript library, and it can be included in your HTML via a <script> tag, here is an example. That should be all you need to do to activate its functionality. It will work transparently in the background.

One final thing I am wondering about is where is the variable "indexedDB" actually defined?

IndexedDB is defined automatically all modern browsers that support it. If window.indexedDB is not defined, then the browser doesn't support it and you'll need to use IndexedDBShim. Also note that this repo only supports the latest versions of Chrome, Firefox and Safari. For additional info on browser support scroll to the bottom of this page.

Let me know how things go once you've referenced the shim in your applications HTML?

if(this.token != undefined){

When referring to snippets of code please confirm the file name and line number.

I'm assuming this is TPKLayer.js? If that's the case, then make sure you follow the concepts outlined in the How to use the TPKLayer library. Best practices include:

andygup commented 9 years ago

@jaarons47 one clarification...make sure you change the file type name from .tpk to .zip There's no need to actually rezip the file. Just changing the file type should do the trick.

jaarons47 commented 9 years ago

Hi Andy, I got it working in Cordova. There were two trouble spots. Here are the changes that I made... 1) In index_basicmap.html "this" variable was not working in Cordova, Have to change to xhr.readyState and blob=xhr.response xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ // if (this.readyState == 4 && this.status == 200){ // blob = this.response; if (xhr.readyState == 4 && xhr.status == 200){ blob = xhr.response; zipParser(blob); xhr.open('GET', 'pdx_World_Street.zip'); xhr.responseType = 'blob'; xhr.send();

2)In offline-tpk-src in _unzipTileFiles: function(files,token,deferred,callback){ a) reader.addEventListener had to be changed to reader.onloadend b) "this" variable also did not work here had to change to reader

//reader.addEventListener("loadend", function (evt) { reader.onloadend = function(evt) { // if(this.token != undefined){ if(reader.token != undefined){ that._inMemTilesIndex.push("blank"); // var name = files[this.token].filename.toLocaleUpperCase(); var name = files[reader.token].filename.toLocaleUpperCase(); //that._inMemTilesObject[name]= this.result; that._inMemTilesObject[name]= reader.result; var size = that.ObjectSize(that._inMemTilesObject); if(size > 0){ callback(deferred,data.token); }; reader.readAsArrayBuffer(data); //open bundleX }

andygup commented 9 years ago

Sweet, glad you got it!

Thanks for the info on the changes. I'll test them on several different devices/browsers + PhoneGap. I'll report back if your pattern is more universal or browser specific. If it's universal then I'll just post the change.

andygup commented 9 years ago

@jaarons47 Looks like your changes in TPKLayer._unzipTileFiles() also work on Chrome, Firefox and Safari. I'll roll them into an update. Thanks!

jaarons47 commented 9 years ago

One added benefit to this that is really important for offline usage. I changed the max DB size to 2000 MB and then loaded up a 220 MB TPK and it worked no problem. By using Cordova and IndexedDBShim it appears that you completely get around the browser cache size limitations. Performance seems really good even at that size!

andygup commented 9 years ago

Indeed, I've seen some pretty good performance as well.

you completely get around the browser cache size limitations

You never really get around the memory limitations, especially on a mobile device.

A note on Android WebView and cache size. When you run PhoneGap on Android it's using WebView. Since Android Lollipop, WebView is much improved. The amount of memory the app + WebView is allowed to use by the OS will vary. If the app uses too much memory then the OS will simply shut it down. The OS looks at how much memory the PhoneGap app is consuming in comparison to available memory, available cached memory and available CPU.

So, when you start looking at how the mobile browser cache works that's when things start getting tricky. For every 1MB stored in IndexedDB the memory footprint of the browser or WebView will grow by an additional factor. It's not a 1:1 relationship. For example, in a recent demo I uploaded a 14MB TPK that injected 1.5MBs of tiles into IndexedDB and took up an additional 196MBs of cached memory. That means the cache grew by a factor of 131 times the original storage value! http://slides.com/andyg/offline-js#/50

If someone is interested in how to manage memory in an Android app here's a good doc.

jaarons47 commented 9 years ago

Thank you for the information Andy. Looking through the documentation I am struggling to find any documentation on a hard maximum memory usage in WebView. It must be quite large though and probably different based on the OS it is run on.

andygup commented 9 years ago

My hunch is that any WebView size limits are rolled into those of the entire PhoneGap application that invokes WebView. And, there aren't any hard, set in stone limits on how much any one native application can use since there are simply too many variables involved.

For example, some common things that the OS takes into consideration at any point in time are how many other applications are running and how much temporary memory plus cached memory each application is using as compared to what's free. As you can imagine, those factors alone will vary from device-to-device and user-to-user.