How to implement a PDF viewer that loads pages asynchronously - android

We need to allow users of our mobile app to browse a magazine with an experience that is fast, fluid and feels native to the platform (similar to iBooks/Google Books).
Some featurs we need are being able to see Thumbnails of the whole magazine, and searching for specific text.
The problem is that our magazines are over 140 pages long and we can’t force our users to have to fully download the whole ebook/PDF beforehand. We need pages to be loaded asynchronously, that is, to let users start reading without having to fully download the content.
I studied PDFKit for iOS however I didn’t find any mention in the documentation about downloading a PDF asynchronously.
Are there any solutions/libraries to implement this functionality on iOS and Android?

What you're looking for is called linearization and according to this answer.
The first object immediately after the %PDF-1.x header line shall
contain a dictionary key indicating the /Linearized property of the
file.
This overall structure allows a conforming reader to learn the
complete list of object addresses very quickly, without needing to
download the complete file from beginning to end:
The viewer can display the first page(s) very fast, before the
complete file is downloaded.
The user can click on a thumbnail page preview (or a link in the ToC
of the file) in order to jump to, say, page 445, immediately after the
first page(s) have been displayed, and the viewer can then request all
the objects required for page 445 by asking the remote server via byte
range requests to deliver these "out of order" so the viewer can
display this page faster. (While the user reads pages out of order,
the downloading of the complete document will still go on in the
background...)
You can use this native library to linearization a PDF.
However
I wouldn't recommend made it has rendering the PDFs wont be fast, fluid or feel native. For those reasons, as far as I know there is no native mobile app that does linearization. Moreover, you have to create your own rendering engine for the PDF as most PDF viewing libraries do not support linearization . What you should do instead is convert the each individual page in the PDF to HTML on the server end and have the client only load the pages when required and cache. We will also save PDFs plan text separately in order to enable search. This way everything will be smooth as the resources will be lazy loaded. In order to achieve this you can do the following.
Firstly
On the server end, whenever you publish a PDF, the pages of the PDF should be split into HTML files as explained above. Page thumbs should also be generated from those pages. Assuming that your server is running on python with a flask microframework this is what you do.
from flask import Flask,request
from werkzeug import secure_filename
import os
from pyPdf import PdfFileWriter, PdfFileReader
import imgkit
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.pdfpage import PDFPage
from pdfminer.converter import XMLConverter, HTMLConverter, TextConverter
from pdfminer.layout import LAParams
import io
import sqlite3
import Image
app = Flask(__name__)
#app.route('/publish',methods=['GET','POST'])
def upload_file():
if request.method == 'POST':
f = request.files['file']
filePath = "pdfs/"+secure_filename(f.filename)
f.save(filePath)
savePdfText(filePath)
inputpdf = PdfFileReader(open(filePath, "rb"))
for i in xrange(inputpdf.numPages):
output = PdfFileWriter()
output.addPage(inputpdf.getPage(i))
with open("document-page%s.pdf" % i, "wb") as outputStream:
output.write(outputStream)
imgkit.from_file("document-page%s.pdf" % i, "document-page%s.jpg" % i)
saveThum("document-page%s.jpg" % i)
os.system("pdf2htmlEX --zoom 1.3 pdf/"+"document-page%s.pdf" % i)
def saveThum(infile):
save = 124,124
outfile = os.path.splitext(infile)[0] + ".thumbnail"
if infile != outfile:
try:
im = Image.open(infile)
im.thumbnail(size, Image.ANTIALIAS)
im.save(outfile, "JPEG")
except IOError:
print("cannot create thumbnail for '%s'" % infile)
def savePdfText(data):
fp = open(data, 'rb')
rsrcmgr = PDFResourceManager()
retstr = io.StringIO()
codec = 'utf-8'
laparams = LAParams()
device = TextConverter(rsrcmgr, retstr, codec=codec, laparams=laparams)
# Create a PDF interpreter object.
interpreter = PDFPageInterpreter(rsrcmgr, device)
# Process each page contained in the document.
db = sqlite3.connect("pdfText.db")
cursor = db.cursor()
cursor.execute('create table if not exists pagesTextTables(id INTEGER PRIMARY KEY,pageNum TEXT,pageText TEXT)')
db.commit()
pageNum = 1
for page in PDFPage.get_pages(fp):
interpreter.process_page(page)
data = retstr.getvalue()
cursor.execute('INSERT INTO pagesTextTables(pageNum,pageText) values(?,?) ',(str(pageNum),data ))
db.commit()
pageNum = pageNum+1
#app.route('/page',methods=['GET','POST'])
def getPage():
if request.method == 'GET':
page_num = request.files['page_num']
return send_file("document-page%s.html" % page_num, as_attachment=True)
#app.route('/thumb',methods=['GET','POST'])
def getThum():
if request.method == 'GET':
page_num = request.files['page_num']
return send_file("document-page%s.thumbnail" % page_num, as_attachment=True)
#app.route('/search',methods=['GET','POST'])
def search():
if request.method == 'GET':
query = request.files['query ']
db = sqlite3.connect("pdfText.db")
cursor = db.cursor()
cursor.execute("SELECT * from pagesTextTables Where pageText LIKE '%"+query +"%'")
result = cursor.fetchone()
response = Response()
response.headers['queryResults'] = result
return response
Here is an explanation of what the flask app is doing.
The /publish route is responsible for the publishing of your magazine, turning very page to HTML, saving the PDFs text to an SQlite db and generating thumbnails for those pages. I've used pyPDF for splitting the PDF to individual pages, pdfToHtmlEx to convert the pages to HTML, imgkit to generate those HTML to images and PIL to generate thumbs from those images. Also, a simple Sqlite db saves the pages' text.
The /page, /thumb and /search routes are self explanatory. They simply return the HTML, thumb or search query results.
Secondly, on the client end you simply download the HTML page whenever the user scrolls to it. Let me give you an example for android OS. Firstly, you'd want to Create some Utils to handle the GET requestrs
public static byte[] GetPage(int mPageNum){
return CallServer("page","page_num",Integer.toString(mPageNum))
}
public static byte[] GetThum(int mPageNum){
return CallServer("thumb","page_num",Integer.toString(mPageNum))
}
private static byte[] CallServer(String route,String requestName,String requestValue) throws IOException{
OkHttpClient client = new OkHttpClient.Builder().connectTimeout(30, TimeUnit.SECONDS).writeTimeout(30, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS).build();
MultipartBody.Builder mMultipartBody = new MultipartBody.Builder().setType(MultipartBody.FORM).addFormDataPart(requestName,requestValue);
RequestBody mRequestBody = mMultipartBody.build();
Request request = new Request.Builder()
.url("yourUrl/"+route).post(mRequestBody)
.build();
Response response = client.newCall(request).execute();
return response.body().bytes();
}
The helper utils above simple handle the queries to the server for you, they should be self explanatory.
Next, you simple create an RecyclerView with a WebView viewHolder or better yet an advanced webview as it will give you more power with customization.
public static class ViewHolder extends RecyclerView.ViewHolder {
private AdvancedWebView mWebView;
public ViewHolder(View itemView) {
super(itemView);
mWebView = (AdvancedWebView)itemView;}
}
private class ContentAdapter extends RecyclerView.Adapter<YourFrament.ViewHolder>{
#Override
public ViewHolder onCreateViewHolder(ViewGroup container, int viewType) {
return new ViewHolder(new AdvancedWebView(container.getContext()));
}
#Override
public int getItemViewType(int position) {
return 0;
}
#Override
public void onBindViewHolder( ViewHolder holder, int position) {
handlePageDownload(holder.mWebView);
}
private void handlePageDownload(AdvancedWebView mWebView){....}
#Override
public int getItemCount() {
return numberOfPages;
}
}
That should be about it.

I am sorry to say, But there is no any library or SDK available which provides asynchronously pages loading functionality. It is next to impossible on the mobile device to open PDF file without downloading the full pdf file.
Solution:
I have already done R&D for the same and fulfilled your requirement in the project. I am not sure iBooks and Google books used below mechanism or not. But is working fine as per your requirements.
Divide your pdf into n number of part (E.g Suppose you have 150 pages in pdf then every pdf contain 15 pages -> It will take some effort from web end.)
Once first part download successfully then display it to the user and other part downloading asynchronously.
After downloading all part of the pdf file, Use below code the merge Pdf file.
How to Merge PDF file
UIGraphicsBeginPDFContextToFile(oldFile, paperSize, nil);
for (pageNumber = 1; pageNumber <= count; pageNumber++)
{
UIGraphicsBeginPDFPageWithInfo(paperSize, nil);
//Get graphics context to draw the page
CGContextRef currentContext = UIGraphicsGetCurrentContext();
//Flip and scale context to draw the pdf correctly
CGContextTranslateCTM(currentContext, 0, paperSize.size.height);
CGContextScaleCTM(currentContext, 1.0, -1.0);
//Get document access of the pdf from which you want a page
CGPDFDocumentRef newDocument = CGPDFDocumentCreateWithURL ((CFURLRef) newUrl);
//Get the page you want
CGPDFPageRef newPage = CGPDFDocumentGetPage (newDocument, pageNumber);
//Drawing the page
CGContextDrawPDFPage (currentContext, newPage);
//Clean up
newPage = nil;
CGPDFDocumentRelease(newDocument);
newDocument = nil;
newUrl = nil;
}
UIGraphicsEndPDFContext();
Reference: How to merge PDF file.
Update:
Main advantage of this mechanism is Logic remain same for all device Android and iOS Device.

Related

ARCore 1.2 Unity Create AugmentedImageDatabase on the fly

I am trying to dynamically create an image database using arcores new image tracking feature.
Currently I have a server serving me image locations which I download to the persistent data path of my device. I use these images to then create new database entries like below:
Public Variables:
public AugmentedImageDatabase newBD;
public AugmentedImageDatabaseEntry newEntry;
Here I do regex matching to get the images from the datapath and convert them to texture2D's in order to populate the AugmentedImageDatabaseEntry values.
Regex r1 = new Regex(#"https?://s3-([^.]+).amazonaws.com/([^/]+)/([^/]+)/(.*)");
// Match the input for file name
Match match = r1.Match(input);
if (match.Success)
{
string v = match.Groups[4].Value;
RegexMatch = v;
Texture2D laodedTexture = LoadTextureToFile(v);
laodedTexture.EncodeToPNG();
AugmentedImageDatabaseEntry newEntry = new AugmentedImageDatabaseEntry(v, laodedTexture, Application.persistentDataPath + "/" + v);
newEntry.Name = v;
newEntry.Texture = laodedTexture;
newEntry.TextureGUID = Application.persistentDataPath + "/" + v;
Debug.Log(newEntry.Name);
Debug.Log(newEntry.Texture);
Debug.Log(newEntry.TextureGUID);
newBD.Add(newEntry);
}
To get this to work on android I had to modify the source of ARCore's unity implementation a little so that the database.Add() function would work outside of the editor.
All of this seems to work seamlessly as I don't get any errors yet.
Once I change scenes to the ARCore scene I instantiate an ARCore Camera and create a new sessionconfig which holds a reference to the database populated above.
Here is that code:
public class NewConfigSetup : MonoBehaviour {
public GameObject downloadManager;
public GameObject arcoreDevice;
// Use this for initialization
void Start () {
downloadManager = GameObject.Find("DownlaodManager");
TestModelGenerator generator = downloadManager.GetComponent<TestModelGenerator>();
GoogleARCore.ARCoreSessionConfig newconfig = new GoogleARCore.ARCoreSessionConfig();
GoogleARCore.ARCoreSessionConfig config = ScriptableObject.CreateInstance<GoogleARCore.ARCoreSessionConfig>();
config.AugmentedImageDatabase = generator.newBD;
Debug.Log("transfered db size --------------- " + config.AugmentedImageDatabase.Count);
arcoreDevice.GetComponent<GoogleARCore.ARCoreSession>().SessionConfig = config;
Instantiate(arcoreDevice,new Vector3(0,0,0), Quaternion.identity);
}
}
When I run in the editor, I dont get errors untill I view the database in the editor, thats when I get this error:
ERROR: flag '--input_image_path' is missing its argument; flag
description: Path of image to be evaluated. Currently only supports
*.png, *.jpg and *.jpeg.
When I debug and look in the memory of the AugmentedImageDatabase. Everything seems to be there and working fine. Also once I build for android I get no errors whatsoever, as well as when I use 'adb logcat -s Unity' in the command line, no exceptions are thrown.
Could this be a limitation with the new ARCore feature? Are the AugmentedImageDatabases not allowing for dynamic creation on android? If so than why are there built in functions for creating them?
I understand the features are brand new and there is not much documentation anywhere so any help would be greatly appreciated.
I posted an Issue on ARCore's Github page, and got a response that the feature you're talking about isn't yet exposed in the Unity API :
https://github.com/google-ar/arcore-unity-sdk/issues/256

ImgCache - How to proper wait for async to finish inside nested for loop

I am building a Cordova app for Android. I have to parse a JSON that consists of posts. Each post has text (title, description, category etc.) and images (an array of images - can be one or more). My aim is to store the JSON data for offline use (save to SQLlite database). Right now the example code below works, but the sequence is not how I expected to be:
request JSON (ok)
Wait for all promises (ok)
Parse JSON (finishes before all the images are downloaded)
Store to database the information but the images still downloading (in background thread - no harm for the UI).
What I would like to have is to store to database, when all the images have been downloaded. I' ve tried many things such as replacing the second for-loop with a recursive function (to handle the async function as stated here) and many other similar approaches but I believe that the problem starts from the 1st for loop which doesn't wait for the checkCache to finish. What do you think? How can I overcome this issue? If you need any further explanation just ask me.
My setup is:
Cordova 4.0.0, Angular 1.3.1 and ImgCache 1.0
My approach is:
1st. Request JSON:
promise1 = $http({method: 'GET', url: baseURL + options1};
promise2 = $http({method: 'GET', url: baseURL + options2};
//...
2nd. Wait for all promises
return $q.all([promise1,promise2,...]).then(function(data){
var promise1size = data[0].data.posts_number;//posts.length;
parseJSON(data[0],promise1size,'category1');
var promise2size = data[1].data.posts_number;//posts.length;
parseJSON(data[1],promise1size,'category2');
//similar for the rest promises
});
3rd. Parse JSON
function parseJSON(respdata,size,category){
console.log("Parsing "+size+" "+category);
for(i=0;i<size;i++){
var item = {};
item ["id"] = respdata.data.object[i].id;
item ["title"] = respdata.data.object[i].title;
item ["description"] = respdata.data.object[i].description;
var jsarray = respdata.data.object[i].categories;
item ["category"] = jsarray[0].title;
item ["catid"] = jsarray[0].id;
//Other JSON keys here similar as above
//Here it starts...
var jsattachement = respdata.data.object[i].attachments;
var atsize = jsattachement.length;
if(atsize>0){
var images=[];
for(j=0;j<atsize;j++){
(function(j){checkCache(jsattachement[j].url)}(j));//here is the problem
multimedia.push({title:item["title"], src:ImgCache.returnCachedURL(jsattachement[j].url), w:400,h:300});
images.push({title:item["title"],src:ImgCache.returnCachedURL(jsattachement[j].url),w:400,h:300});
}
item ["attachement"] = images;
}else
item ["attachement"] = [];
if(category=='category1')
response.category1.push(item);
else if(category=='category2')
response.category2.push(item);
//else if...
//else if...
}
}
};
checkCache function:
function checkCache (imgsrc){
ImgCache.isCached(imgsrc, function(src, success) {
if(!success){
ImgCache.cacheFile(src, function(){});
}
});
};
4th. Store to database
Here I save the parsed information to the database. On step 3 I use the returnCachedURL function for the images (which is not asynchronous) so to have the local path of the image ready even if it might not have been downloaded yet (but eventually will).
I did this:
Similar to you but, use update sql to store the image for every downloaded image. Then, I found that some of my users want to play with me, they disconnect the internet connection in the middle of image downloading! so that I have incomplete record in the sql! Jesus!
then I change to this: I create a temporary global var. e.g. tempvar = {}, then: tempvar.data1 = 'hello', tempvar.preparedatacompleted = true, tempvar.ImagesToBeDownload = 5, tempvar.ImagesDownloaded = 0, tempvar.imagearray = ......
then, everytime image downloaded, add 1 to the number, if that no. = total no., call the function that save all data and images in this tempvar to the sql.

Caching ListFragment data

My application generates ListFragments based on user type (determined by the DeviceID) and then fetch images from a web server. These images will then be displayed as a list.
My question is how can I cache those images as well as fragments (generated dynamically) to be displayed in offline mode. For instance when user opens the application without having an active internet connection, it should display the images within the fragments generated dynamically last time.
At the moment my app just download the images from a web service each time.
code for generating fragments dynamically in the MainActivity each time when the application loads.
//generating the views based on JSON data
try {
JSONObject resultObject = new JSONObject(result.toString());
boolean success = resultObject.getBoolean("success");
JSONArray jArray = resultObject.getJSONArray("data");
if (success == true) {
//save menu
for (int i = 0; i < jArray.length(); i++) {
postObject = jArray.getJSONObject(i);
if (postObject.has("ev_count")) {
categoriesSet.put("Events", "Events");
mTabsAdapter.addTab(bar.newTab().setText("Events"), EventsFragment.class, null);
}
if (postObject.has("pl_count")) {
categoriesSet.put("Places", "Places");
mTabsAdapter.addTab(bar.newTab().setText("Places"), PlacesFragment.class, null);
}
if (postObject.has("gn_count")) {
categoriesSet.put("General", "General");
mTabsAdapter.addTab(bar.newTab().setText("General"), GeneralFragment.class, null);
}
}
}
//saving values to the shared preferences (hashmap as a string)
categoriesPreferences = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = categoriesPreferences.edit();
editor.putString("categories", categoriesSet.toString());
editor.commit();
}catch (JSONException e) {
Log.e("ALLOCATE_DAT_ERROR", e.toString());
}
I would just advice to use some image downloading library which also often handles caching for you..
Here is a short list of some of them:
Volley - directly from google, its more like a whole networking stack, but it allows you to easily download Images (see NetworkImageView) and also to cache them (you need to provide a cache implemtantion - google will help you)
Picasso - nice library from square with very straightforward API - I would advice you to go with it, it might be the easiest way to go
Universal Image Loader - another option, it has really a lot of options and is also easy to use
Here is a simple sample code which should advice you how to use volley to load and cache images.
NetworkImageView mage = (NetworkImageView) view.findViewById(...);
image.setImageUrl("http://someurl.com/image.png",mImageLoader);
You need an ImageLoader instance then..
public ImageLoader getImageLoader() {
if (mImageLoader == null) {
mImageLoader = new ImageLoader(getImageRequestQueue(), new DiskBitmapCache(getCacheDir(), 50 * 1024 * 1024));
}
return mImageLoader;
}
imageRequestQueue is standard queue you should have already initialised somewhere in your app if already using volley for networking stuff
As a DiskCache you can use this

android webview encrypted content

I have an application that uses a webview in order to display content and the Javascript calls are the controller of my application.
In order to provide a level of security I obfuscated the code. This is not enough as I would like to encrypt the html and js files and then decrypt them at runtime. I packed the apk file with these resources encrypted with RC4 algorithm. When loading the files, I am decrypting the javascript files, load them and then decrypt the html file and load it. However this doesn't work as the webcontent displays a message in the form of: the web page at data:text/html might be temporarily down or it may have moved permanently, etc, etc.
I overloaded onLoadResource in order to see what content is loaded and I can see it loads the Javascript content, but the content loaded is html escaped also.
My questions are:
1. How to secure the html and javascript files (located in assets folder) in order to not be accessible?
2. In case my approach is correct, has anyone any idea on what I am doing wrong?
Thanks!
Below is the code that decrypts and loads the resources:
protected void loadWebContent() {
checkEncryptionEnabled();
loadJSFiles();
logger.info("Loaded js ... going for html");
loadAssetFile("www/index.html", "text/html");
}
private void loadJSFiles() {
String[] jsFilesArray = { "app.js", "iscroll.js", "iui.js", "json.js" };
for (String js : jsFilesArray) {
loadAssetFile("www/js/" + js, "application/javascript");
}
}
private void loadAssetFile(String filePath, String mimeType) {
AssetManager assetMgr = getAssets();
InputStream is = null;
try {
is = assetMgr.open(filePath);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] temp = new byte[512];
int bytesRead = -1;
while ((bytesRead = is.read(temp)) > 0) {
baos.write(temp, 0, bytesRead);
}
byte[] encrypted = baos.toByteArray();
String content = null;
/**
* true
* */
if (Config.ENCRYPTION_ENABLED) {
byte[] decrypted = new RC4Encrypter("rc4_key").rc4(encrypted);
content = new String(decrypted, "utf-8");
} else {
content = new String(encrypted, "utf-8");
}
/**
* The webview to use
* */
if("application/javascript".equals(mimeType)) {
webContent.loadUrl("javascript:" + content);
} else {
webContent.loadData(content, mimeType, "utf-8");
}
} catch (IOException ex) {
logger.error(null, ex);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
found the answer for the second question question instead of: webContent.loadData(content, mimeType, "utf-8"); I used: webContent.loadDataWithBaseURL("file:///android_asset/www/", content, mimeType, "utf-8", null); Content is shown with no problems ... However, the first question kind of stands and not; but considering there was no answer for more than a year, I'll consider encrypting data is OK.
Data encryption is OK as long as you can also keep the decryption key confidential, which is not the case in the above code. The hardcoded decryption key can be easily spotted after decompiling the DEX files embedded inside the APK.
If you want to hide the application logic inside the HTML and JavaScript files and if that application logic doesn't require offline capabilities then you could outsource the code of that application logic on a server.
From here you have two choices:
Load the application code dynamically from the server whenever
you need it (and run the application code on the client).
Implement the application logic on the server side, e.g., as a
web service (and run the application code on the server, the client
knows only how to call the web service)
The short answer to your first question is that there is no methodology or technology to perfectly protect your application. I recommend to you to take a look at How to avoid reverse engineering of an APK file? for an overview of possible protection methods.

How to send SharedObject Array data to PHP WS for DB Query (Flex)

I am currently developing an Android application using Flex 4.5.1 and I am having an issue when trying to pass data that I have stored in a SharedObject array to my Web Service for a Database query. the code below shows how I am storing the data in the SharedObject:
var so:SharedObject = SharedObject.getLocal("app");
public var prefsArray:ArrayCollection = new ArrayCollection(so.data.prefs);
protected function prefs_btn_click(event:MouseEvent):void
{
prefsArray.source.push(getFrsByIDResult.lastResult.id);
so.data.prefs = [prefsArray];
var flushStatus:String = so.flush();
if (flushStatus != null) {
switch(flushStatus) {
case SharedObjectFlushStatus.PENDING:
so.addEventListener(NetStatusEvent.NET_STATUS,
onFlushStatus);
break;
case SharedObjectFlushStatus.FLUSHED:
trace("success");
break;
}
}
}
protected function onFlushStatus(event:NetStatusEvent):void
{
trace(event.info.code);
}
I have tested the SharedObject to see if the information is being entered into it correctly and all seems fine. Now I have used the code below in order to retrieve the data from the SharedObject and try and send it to the PHP web Service to run the DB query.
var so:SharedObject = SharedObject.getLocal("app");
var arrCol:ArrayCollection = new ArrayCollection(so.data.prefs);
var str:String = new String(arrCol.toString());
protected function list_creationCompleteHandler(event:FlexEvent):void
{
getPrefsByprefIdsResult.token = prefsService.getPrefsByPrefIds(so.data.prefs);
}
I have tested the Webservice in Flex and have it configured to recieve an Array of Ints (int[]) and it works when i run a test operation on it with two dummy values. However when I try to use the code above to pass the Web Service the Shared Object data I get this error:
TypeError: Error #1034: Type Coercion failed: cannot convert []#97e97e1 to mx.collections.ArrayCollection.
at views::**************/list_creationCompleteHandler()[C:\Users\Jack\Adobe Flash Builder 4.5\****************\src\views\*******************.mxml:25]
at views::*********************/__list_creationComplete()[C:\Users\Jack\Adobe Flash Builder 4.5\****************\src\views\***************.mxml:94]
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at mx.core::UIComponent/dispatchEvent()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\core\UIComponent.as:13128]
at mx.core::UIComponent/set initialized()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\core\UIComponent.as:1818]
at mx.managers::LayoutManager/validateClient()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\managers\LayoutManager.as:1090]
at mx.core::UIComponent/validateNow()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\core\UIComponent.as:8067]
at spark.components::ViewNavigator/commitNavigatorAction()[E:\dev\4.5.1\frameworks\projects\mobilecomponents\src\spark\components\ViewNavigator.as:1878]
at spark.components::ViewNavigator/commitProperties()[E:\dev\4.5.1\frameworks\projects\mobilecomponents\src\spark\components\ViewNavigator.as:1236]
at mx.core::UIComponent/validateProperties()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\core\UIComponent.as:8209]
at mx.managers::LayoutManager/validateProperties()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\managers\LayoutManager.as:597]
at mx.managers::LayoutManager/doPhasedInstantiation()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\managers\LayoutManager.as:783]
at mx.managers::LayoutManager/doPhasedInstantiationCallback()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\managers\LayoutManager.as:1180]
I have replaced certain filenames and locations with *'s to protect the work i am doing, but can someone please help me with this issues as I believe it has to be something simple???
Thanks
ok so let me explain in more detail. This is being designed for an Android app like I said, but image what I am trying to do is to store Bookmarks persistently using the Local Shared Object.
The first chunck of code you see above is designed to create the LSO attribute for the bookmark i want to create and imagine that there can be more than one bookmark set at different times like in a web browser. The only way i could find to do this was to store these items/details in an array which I retrieve and then update before saving back to the LSO and saving.
The second piece of code related to imagine a "Bookmarks Page" with a list of all the content that I have bookmarked. Now what I wanted to happen was thta I would be able to call up the LSO attribute which held the id's of the bookmarks and then load up thier details in a list format.
I have managed to create the LSO and store the bookmark deatils in and allow them to be updated and entries added. Also I have made sure that the PHP code that I have pulls back all the database objects relating to the array of id's and this has been tested using flex. The only thing that I cant seem to do is to pass the id's to the PHP web service file. The code in the Web Service file is below if that helps:
public function getPrefsByPrefIds($PrefIds) {
$stmt = mysqli_prepare($this->connection, "SELECT * FROM $this->tablename WHERE $this->tablename.id IN(" .implode(",", $PrefIds). ")");
$this->throwExceptionOnError();
mysqli_stmt_execute($stmt);
$this->throwExceptionOnError();
$rows = array();
mysqli_stmt_bind_result($stmt, $row->id, $row->name, $row->desc);
while (mysqli_stmt_fetch($stmt)) {
$rows[] = $row;
$row = new stdClass();
mysqli_stmt_bind_result($stmt, $row->id, $row->name, $row->desc);
}
mysqli_stmt_free_result($stmt);
mysqli_close($this->connection);
return $rows;
}
Yes I had already tried that but thanks. I have made some more progress on my own as I have been experimenting with the different types of objects that can be stored in SharedObjects. I have managed to get the solution part working with this code:
This code is designed to capture the boomark info and store it in an arrayCollection before transferring it to a bytesArray and saving
var so:SharedObject = SharedObject.getLocal("app");
public var prefArray:ArrayCollection = new ArrayCollection(so.data.prefs);
protected function prefs_btn_click(event:MouseEvent):void
{
prefArray.source.push(getCompaniesByIDResult.lastResult.id);
so.data.prefs = [prefArray];
var bytes:ByteArray = new ByteArray();
bytes.writeObject(prefArray);
so.data.ac = bytes;
var flushStatus:String = so.flush();
if (flushStatus != null) {
switch(flushStatus) {
case SharedObjectFlushStatus.PENDING:
so.addEventListener(NetStatusEvent.NET_STATUS,
onFlushStatus);
break;
case SharedObjectFlushStatus.FLUSHED:
trace("success");
break;
}
}
}
protected function onFlushStatus(event:NetStatusEvent):void
{
trace(event.info.code);
}
This next code is the designed to retrieve that information from the SahredObjects bytesArray and put it back into an Array Collection
var so:SharedObject = SharedObject.getLocal("app");
var ba:ByteArray = so.data.ac as ByteArray;
var ac:ArrayCollection;
protected function list_creationCompleteHandler(event:FlexEvent):void
{
ba.position = 0;
ac = ba.readObject() as ArrayCollection;
getPrefsByPrefIdsResult.token = prefsService.getPrefsByPrefIds(ac);
}
however as I have said this works in a small way only as if I store only one Bookmark (id) for an item and then go to the bookmarks list the details for that bookark are successfully retrieved, however if I save more than one Bookmark(2 or more id's) the page will not load the details, i do not get an error but I believe it is hanging because it is looking for say id's "1,2" instead of "1" and "2" but i dont know why this is or how to resolve this. I appreciate the advice I have been given but am finding it hard there is no one who can help me with this issue and I am having to do various experiemnts with the code. Can someone please help me with this I would really appreciate it :-) Thanks

Categories

Resources