I'm going to use a pre-populated SQLite database in an Android game which I'm creating using Unity .
Since the simple way doesn't work on Android (It works perfect on Windows thou), I've followed this tutorial to use my database in an Android app.
public void OpenDB(string p) //p is the database name
{
// check if file exists in Application.persistentDataPath
string filepath = Application.persistentDataPath + "/" + p;
if(!File.Exists(filepath))
{
// if it doesn't ->
// open StreamingAssets directory and load the db ->
WWW loadDB = new WWW("jar:file://" + Application.dataPath + "!/assets/" + p); // this is the path to your StreamingAssets in android
while(!loadDB.isDone) {} // CAREFUL here, for safety reasons you shouldn't let this while loop unattended, place a timer and error check
// then save to Application.persistentDataPath
File.WriteAllBytes(filepath, loadDB.bytes);
}
//open db connection
connection = "URI=file:" + filepath;
dbcon = new SqliteConnection(connection);
dbcon.Open();
}
When I run this code, I get following error:
SqliteSyntaxException: file is encrypted or is not a database
Here's the full error:
SqliteSyntaxException: file is encrypted or is not a database
Mono.Data.SqliteClient.SqliteCommand.GetNextStatement (IntPtr pzStart,
System.IntPtr& pzTail, System.IntPtr& pStmt)
Mono.Data.SqliteClient.SqliteCommand.ExecuteReader (CommandBehavior
behavior, Boolean want_results, System.Int32& rows_affected)
Mono.Data.SqliteClient.SqliteCommand.ExecuteReader (CommandBehavior
behavior) Mono.Data.SqliteClient.SqliteCommand.ExecuteDbDataReader
(CommandBehavior behavior) System.Data.Common.DbCommand.ExecuteReader
() System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader
() dbAccess.SingleSelectWhere (System.String tableName, System.String
itemToSelect, System.String wCol, System.String wPar, System.String
wValue) (at Assets/dbAccess.cs:152)
I've downloaded the example from that post, and got the same error.
I'm suspicious about this line:
File.WriteAllBytes(filepath, loadDB.bytes);
and my thought is for some reason, it can't write the database data into the file.
Does anyone know how to solve this?
I solved the problem thanks to this blog.
This code works perfect on every platform due to the if statements which will do different behaviors depending on which platform the app is running on.
Here's the DataService.cs which do the important parts (or maybe I better say the whole part)
using SQLite4Unity3d;
using UnityEngine;
#if !UNITY_EDITOR
using System.Collections;
using System.IO;
#endif
using System.Collections.Generic;
public class DataService {
private SQLiteConnection _connection;
public DataService(string DatabaseName){
#if UNITY_EDITOR
var dbPath = string.Format(#"Assets/StreamingAssets/{0}", DatabaseName);
#else
// check if file exists in Application.persistentDataPath
var filepath = string.Format("{0}/{1}", Application.persistentDataPath, DatabaseName);
if (!File.Exists(filepath))
{
Debug.Log("Database not in Persistent path");
// if it doesn't ->
// open StreamingAssets directory and load the db ->
#if UNITY_ANDROID
var loadDb = new WWW("jar:file://" + Application.dataPath + "!/assets/" + DatabaseName); // this is the path to your StreamingAssets in android
while (!loadDb.isDone) { } // CAREFUL here, for safety reasons you shouldn't let this while loop unattended, place a timer and error check
// then save to Application.persistentDataPath
File.WriteAllBytes(filepath, loadDb.bytes);
#elif UNITY_IOS
var loadDb = Application.dataPath + "/Raw/" + DatabaseName; // this is the path to your StreamingAssets in iOS
// then save to Application.persistentDataPath
File.Copy(loadDb, filepath);
#elif UNITY_WP8
var loadDb = Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS
// then save to Application.persistentDataPath
File.Copy(loadDb, filepath);
#elif UNITY_WINRT
var loadDb = Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS
// then save to Application.persistentDataPath
File.Copy(loadDb, filepath);
#else
var loadDb = Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS
// then save to Application.persistentDataPath
File.Copy(loadDb, filepath);
#endif
Debug.Log("Database written");
}
var dbPath = filepath;
#endif
_connection = new SQLiteConnection(dbPath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create);
Debug.Log("Final PATH: " + dbPath);
}
public void CreateDB(){
_connection.DropTable<Person> ();
_connection.CreateTable<Person> ();
_connection.InsertAll (new[]{
new Person{
Id = 1,
Name = "Tom",
Surname = "Perez",
Age = 56
},
new Person{
Id = 2,
Name = "Fred",
Surname = "Arthurson",
Age = 16
},
new Person{
Id = 3,
Name = "John",
Surname = "Doe",
Age = 25
},
new Person{
Id = 4,
Name = "Roberto",
Surname = "Huertas",
Age = 37
}
});
}
public IEnumerable<Person> GetPersons(){
return _connection.Table<Person>();
}
public IEnumerable<Person> GetPersonsNamedRoberto(){
return _connection.Table<Person>().Where(x => x.Name == "Roberto");
}
public Person GetJohnny(){
return _connection.Table<Person>().Where(x => x.Name == "Johnny").FirstOrDefault();
}
public Person CreatePerson(){
var p = new Person{
Name = "Johnny",
Surname = "Mnemonic",
Age = 21
};
_connection.Insert (p);
return p;
}
}
It follows by two other script to create or use the existing database.
ExistingDBScript.cs
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
public class ExistingDBScript : MonoBehaviour {
public Text DebugText;
// Use this for initialization
void Start () {
var ds = new DataService ("existing.db");
//ds.CreateDB ();
var people = ds.GetPersons ();
ToConsole (people);
people = ds.GetPersonsNamedRoberto ();
ToConsole("Searching for Roberto ...");
ToConsole (people);
ds.CreatePerson ();
ToConsole("New person has been created");
var p = ds.GetJohnny ();
ToConsole(p.ToString());
}
private void ToConsole(IEnumerable<Person> people){
foreach (var person in people) {
ToConsole(person.ToString());
}
}
private void ToConsole(string msg){
DebugText.text += System.Environment.NewLine + msg;
Debug.Log (msg);
}
}
CreateDBScript.cs
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
public class CreateDBScript : MonoBehaviour {
public Text DebugText;
// Use this for initialization
void Start () {
StartSync();
}
private void StartSync()
{
var ds = new DataService("tempDatabase.db");
ds.CreateDB();
var people = ds.GetPersons ();
ToConsole (people);
people = ds.GetPersonsNamedRoberto ();
ToConsole("Searching for Roberto ...");
ToConsole (people);
}
private void ToConsole(IEnumerable<Person> people){
foreach (var person in people) {
ToConsole(person.ToString());
}
}
private void ToConsole(string msg){
DebugText.text += System.Environment.NewLine + msg;
Debug.Log (msg);
}
}
And the person script, demonstrates the person table in the database
using SQLite4Unity3d;
public class Person {
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int Age { get; set; }
public override string ToString ()
{
return string.Format ("[Person: Id={0}, Name={1}, Surname={2}, Age={3}]", Id, Name, Surname, Age);
}
}
Also you need to add plugins and Sqlite.cs to your project which you can find in the git repository
It helped me to overcome the issue, Hope it help the others as well.
Related
So here's my code, it works perfectly fine in unity editor but not on android, it only reads the first line of the xml file.
I don't know what is wrong with it.
The file exists on the phone but it only reads the first line.
In the unity editor it works perfectly fine.
Thank for your help.
using System.Xml;
using System.Xml.Serialization;
public class Mission
{ [XmlAttribute("id")]
public int Id;
public string description;
public int ordre;
}
Here is the class that loads the xml
using System.IO;
using System.Xml.Serialization;
using System.Collections.Generic;
[XmlRoot("Missionss")]
public class Missions{
[XmlArray("Missions"), XmlArrayItem("Mission")]
public List<Mission> Mission_List;
private Missions(){}
public static Missions LoadFromFile(string filepath){
XmlSerializer serializer = new XmlSerializer(typeof(Missions));
using(FileStream stream = new FileStream(filepath, FileMode.Open))
{
return serializer.Deserialize(stream) as Missions;
}
}
public void Save (string path)
{
XmlSerializer serializer = new XmlSerializer (typeof(Missions));
using (FileStream stream = new FileStream (path, FileMode.Create)) {
serializer.Serialize (stream, this);
}
}
}
and finally:
public void LoadXML(){
string filename="/phases.xml";
string filename1="/missions.xml";
phases = Phases.LoadFromFile(Application.persistentDataPath + filename);
missions = Missions.LoadFromFile (Application.persistentDataPath + filename1);
}
public void save(){
phases.Save(Application.streamingAssetsPath + "phases.xml");
missions.Save(Application.streamingAssetsPath + "missions.xml");
}
dont you need "\" written somewhere? lets say: phases.Save(Application.streamingAssetsPath +"\\"+ "phases.xml");
I am trying to get the app code and display it, for an example if button X starts a new activity then a textView displays the whole method
I reached only how can I display code in HTML format from this question
But is there is a way to get the code of my app out, I think that there are 2 ways
An Internal one by getting it by the app itself
An External one by reading the java file then filtering it and getting the text of the method
Is there are any ideas about that?
Thanks in advance
The above is not currently possible as mentioned by others is the comments. What i can suggest is shipping your application with the source code in the assets folder and using a helper function to extract a certain methods from the source at runtime (your second proposed approach). I have written example code but it is in pure java and needs to be ported to android (a few lines).
NB: You may need to reformat the code after extraction depending on your use case.
Hope it helps :)
The code for the helper method:
static String getTheCode(String classname ,String methodSignature ) throws FileNotFoundException {
//**********************A few lines of code below need changing when porting ***********//
// open file, your will be in the assets folder not in the home dir of user, don't forget the .java extension when porting
File file = new File(System.getProperty("user.home") +"/"+ classname +".java");
// get the source, you can use FileInputReader or some reader supported by android
Scanner scanner = new Scanner(file);
String source = "";
while(scanner.hasNext()) {
source += " "+ scanner.next();
}
//**********************The above code needs changing when porting **********//
// extract code using the method signature
methodSignature = methodSignature.trim();
source = source.trim();
//appending { to differentiate from argument as it can be matched also if in the same file
methodSignature = methodSignature+"{";
//making sure we find what we are looking for
methodSignature = methodSignature.replaceAll("\\s*[(]\\s*", "(");
methodSignature = methodSignature.replaceAll("\\s*[)]\\s*", ")");
methodSignature = methodSignature.replaceAll("\\s*[,]\\s*", ",");
methodSignature = methodSignature.replaceAll("\\s+", " ");
source =source.replaceAll("\\s*[(]\\s*", "(");
source = source.replaceAll("\\s*[)]\\s*", ")");
source = source.replaceAll("\\s*[,]\\s*", ",");
source = source.replaceAll("\\s+", " ");
if(!source.contains(methodSignature)) return null;
// trimming all text b4 method signature
source = source.substring(source.indexOf(methodSignature));
//getting last index, a methods ends when there are matching pairs of these {}
int lastIndex = 0;
int rightBraceCount = 0;
int leftBraceCount = 0;
char [] remainingSource = source.toCharArray();
for (int i = 0; i < remainingSource.length ; i++
) {
if(remainingSource[i] == '}'){
rightBraceCount++;
if(rightBraceCount == leftBraceCount){
lastIndex = (i + 1);
break;
}
}else if(remainingSource[i] == '{'){
leftBraceCount++;
}
}
return source.substring(0 ,lastIndex);
}
Example usage (getTheCode methods is static and in a class called GetTheCode):
public static void main(String... s) throws FileNotFoundException {
System.out.println(GetTheCode.getTheCode("Main", "private static void shoutOut()"));
System.out.println(GetTheCode.getTheCode("Main", "private static void shoutOut(String word)"));
}
Output:
private static void shoutOut(){ // nothing to here }
private static void shoutOut(String word){ // nothing to here }
NB: When starting your new activity create a method eg
private void myStartActivty(){
Intent intent = new Intent(MyActivity.this, AnotherActivity.class);
startActivity(intent);
}
Then in your onClick:
#Override
public void onClick(View v) {
myStartActivity();
myTextView.setText(GetTheCode.getTheCode("MyActivity","private void myStartActivity()"));
}
Update: Ported the Code for android:
import android.content.Context;
import java.io.IOException;
import java.util.Scanner;
public class GetTheCode {
static String getTheCode(Context context, String classname , String methodSignature ) {
Scanner scanner = null;
String source = "";
try {
scanner = new Scanner(context.getAssets().open(classname+".java"));
while(scanner.hasNext()) {
source += " "+ scanner.next();
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
scanner.close();
// extract code using the method signature
methodSignature = methodSignature.trim();
source = source.trim();
//appending { to differentiate from argument as it can be matched also if in the same file
methodSignature = methodSignature+"{";
//making sure we find what we are looking for
methodSignature = methodSignature.replaceAll("\\s*[(]\\s*", "(");
methodSignature = methodSignature.replaceAll("\\s*[)]\\s*", ")");
methodSignature = methodSignature.replaceAll("\\s*[,]\\s*", ",");
methodSignature = methodSignature.replaceAll("\\s+", " ");
source =source.replaceAll("\\s*[(]\\s*", "(");
source = source.replaceAll("\\s*[)]\\s*", ")");
source = source.replaceAll("\\s*[,]\\s*", ",");
source = source.replaceAll("\\s+", " ");
if(!source.contains(methodSignature)) return null;
// trimming all text b4 method signature
source = source.substring(source.indexOf(methodSignature));
//getting last index, a methods ends when there are matching pairs of these {}
int lastIndex = 0;
int rightBraceCount = 0;
int leftBraceCount = 0;
char [] remainingSource = source.toCharArray();
for (int i = 0; i < remainingSource.length ; i++
) {
if(remainingSource[i] == '}'){
rightBraceCount++;
if(rightBraceCount == leftBraceCount){
lastIndex = (i + 1);
break;
}
}else if(remainingSource[i] == '{'){
leftBraceCount++;
}
}
return source.substring(0,lastIndex);
}
}
Usage:
// the method now takes in context as the first parameter, the line below was in an Activity
Log.d("tag",GetTheCode.getTheCode(this,"MapsActivity","protected void onCreate(Bundle savedInstanceState)"));
Let's start with a broader overview of the problem:
Display App code
Press X button
Open new activity with a textview which displays the method
The goal is to do the following:
Viewing app method by extracting it and then building & running it.
There are some methods we can use to run Java/Android code dynamically. The way I would personally do it is DexClassLoader and with Reflection.
If you need more details, let me know. Here is what it'd do though:
View app method
Upon pressing X, launch intent with extra to new Activity
Parse and compile code dynamically and then run it with DexClassLoader and Reflection
Sources:
Sample file loading Java method from TerminalIDE Android App
Android Library I made for Auto-Updating Android Applications without needing the Play Store on non-root devices
I wrote some code to get all the folders inside a folder inside the StreamingAssets and then to get all the files in that folder.
It works well on Windows but I can't get it to work on Android.
Here is the code :
foreach (string s in Directory.GetDirectories(model.path + "/Levels")) {
GameObject el = (GameObject)Instantiate(listButton, Vector3.zero, Quaternion.identity);
el.transform.SetParent(grid1.transform);
string[] words = s.Split('/');
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
words = s.Split('\\');
#endif
el.GetComponentInChildren<Text>().text = words[words.Length - 1];
el.GetComponent<Button>().onClick.AddListener(() => {
MapSizeButtonClick(Int32.Parse(el.GetComponentInChildren<Text>().text));
});
}
I had to add the #if UNITY_STANDALONE_WIN for windows because of the '\'.
Then for the files inside that folder I wrote this :
foreach (string s in Directory.GetFiles(model.path + "/Levels/" + model.mapSize + "/" + model.colorNumber)) {
string[] words = s.Split('/');
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
words = s.Split('\\');
#endif
words = words[words.Length - 1].Split('.');
if (words[1] == "json") {
GameObject el = (GameObject)Instantiate(listButton, Vector3.zero, Quaternion.identity);
el.transform.SetParent(grid3.transform);
el.GetComponentInChildren<Text>().text = words[0];
el.GetComponent<Button>().onClick.AddListener(() => {
levelButtonClick(Int32.Parse(el.GetComponentInChildren<Text>().text));
});
}
}
I'm using UnityEngine.UI.Directory but I read that it can't be use on Android and I must use WWW instead. Something like this :
string xmlSetUpPath = "jar:file://" + Application.dataPath + "!/assets/xml/xmlSetUp.xml";
WWW book1WWW = new WWW(book1Path);
yield return book1WWW;
if(!File.Exists(Application.persistentDataPath + "/xml/book1.xml"))
{
File.WriteAllBytes(Application.persistentDataPath + "/xml/book1.xml", book1WWW.bytes);
}
However I couldn't find anything like Directory.getFiles() or Directory.getFolder(). So I thought I could use WWW to check if the file exists (as the file names are 1.json, 2.json, 3.json) and iterate until a file doesn't exist.
But I would also need to check if a folder exists for the 1st part of the script and I can't find how to check if a folder exists with WWW. (My folders names are also 1,2,3,4...)
So how to check if a folder exists with WWW ?
Or what else can I do ?
UPDATE :
I updated the code, now it looks like this :
for (int i = 0; i < 100; i++) {
if (Directory.Exists(Path.Combine(model.path, "Levels/" + i.ToString()))){
GameObject el = (GameObject)Instantiate(listButton, Vector3.zero, Quaternion.identity);
el.transform.SetParent(grid1.transform);
el.GetComponentInChildren<Text>().text = i.ToString();
el.GetComponent<Button>().onClick.AddListener(() => {
MapSizeButtonClick(Int32.Parse(el.GetComponentInChildren<Text>().text));
});
}
}
I use path.combine() and check every folder from 0 to 100. It works on Windows but it still doesn't work in Android.
The path is set like this :
#if UNITY_IPHONE
path = Application.dataPath + "/Raw";
#endif
#if UNITY_ANDROID
path = "jar:file://" + Application.dataPath + "!/assets";
#endif
#if UNITY_STANDALONE || UNITY_EDITOR
path = Application.dataPath + "/StreamingAssets";
#endif
QUESTION UPDATE
I made a script that generate the file paths. The paths are always the same : "FolderNumerotedFromYToX/FolderNumerotedFromYToX/FileNumerotedFrom1ToX.json". The generation works but the writing to JSON doesn't. It outputs "{}".
// Generate level list
List<KeyValuePair<int, List<KeyValuePair<int, List<int>>>>> l;
l = new List<KeyValuePair<int, List<KeyValuePair<int, List<int>>>>>();
for (int i = 1; i < 100; i++) {
if (Directory.Exists(Path.Combine(Path.Combine(Application.dataPath, "StreamingAssets"), "Levels/" + i.ToString()))) {
l.Add(new KeyValuePair<int, List<KeyValuePair<int, List<int>>>>(i, new List<KeyValuePair<int, List<int>>>()));
for (int j = 1; j < 100; j++) {
if (Directory.Exists(Path.Combine(Path.Combine(Application.dataPath, "StreamingAssets"), "Levels/" + i + "/" + j))) {
l[l.Count - 1].Value.Add(new KeyValuePair<int, List<int>>(j, new List<int>()));
int k = 1;
while (true) {
if (File.Exists(Path.Combine(Path.Combine(Application.dataPath, "StreamingAssets"), "Levels/" + i + "/" + j + "/" + k + ".json"))) {
l[l.Count - 1].Value[l[l.Count - 1].Value.Count - 1].Value.Add(k);
}
else
break;
k++;
}
}
}
}
}
string ljson = JsonUtility.ToJson(l);
var lsr = File.CreateText("Assets/StreamingAssets/levels.json");
lsr.WriteLine(ljson);
lsr.Close();
Can you help me finding a way to store this to a file ?
It may sound hard, but you really have to use the WWW class to access your files from StreamingAssets on Android.
And you have to keep a list of your filenames, to be able to access them, as there seems to be no way to use something like File.Exists or Directory.GetFiles.
Example:
WWW www = new WWW(source);
yield return www;
if (string.IsNullOrEmpty(www.error))
{
// make sure the destination directory exists
File.WriteAllBytes(destination, www.bytes);
}
else
{
// error handling
}
Your source files (base) path should look like this:
jar:file://" + Application.dataPath + "!/assets/
From the docs:
Note that on Android, the files are contained within a compressed .jar
file (which is essentially the same format as standard zip-compressed
files). This means that if you do not use Unity’s WWW class to
retrieve the file then you will need to use additional software to see
inside the .jar archive and obtain the file.
Source: http://docs.unity3d.com/Manual/StreamingAssets.html
To solve this problem I created pre-compilation script which will run before the compilation; this script will list the names of the files you want to access later in text file
#if UNITY_EDITOR
using UnityEditor.Build;
using UnityEditor;
using System.IO;
public class BM_AndroidBuildPrepartion : IPreprocessBuild
{
public int callbackOrder { get { return 0; } }
public void OnPreprocessBuild(BuildTarget target, string path)
{
// Do the preprocessing here
string[] fileEntries = Directory.GetFiles("Assets/Resources/Prefabs/alphabet", "*.prefab");
System.IO.Directory.CreateDirectory("Assets/StreamingAssets/");
using (StreamWriter sw = new StreamWriter("Assets/StreamingAssets/alphabet.txt", false))
{
foreach (string filename in fileEntries) {
sw.WriteLine(Path.GetFileNameWithoutExtension(filename));
}
}
}
}
#endif
then I read the text file and you can access the files in same way but you have to put them inside your project in Assets/StreamingAssets/
#if UNITY_ANDROID
string path = "jar:file://" + Application.dataPath + "!/assets/alphabet.txt";
WWW wwwfile = new WWW(path);
while (!wwwfile.isDone) { }
var filepath = string.Format("{0}/{1}", Application.persistentDataPath, "alphabet.t");
File.WriteAllBytes(filepath, wwwfile.bytes);
StreamReader wr = new StreamReader(filepath);
string line;
while ((line = wr.ReadLine()) != null)
{
//your code
}
#endif
I am developing a Cordova plugin for Android and I am having difficulty overcoming accessing project resources from within an activity - the plugin should be project independent, but accessing the resources (e.g. R.java) is proving tricky.
My plugin, for now, is made up of two very simple classes: RedLaser.java and RedLaserScanner.java.
RedLaser.java
Inherits from CordovaPlugin and so contains the execute method and looks similar to the following.
public class RedLaser extends CordovaPlugin {
private static final string SCAN_ACTION = "scan";
public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
if (action.equals(SCAN_ACTION)) {
this.cordova.getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
scan(args, callbackContext);
}
});
return true;
}
return false;
}
private void scan(JSONArray args, CallbackContext callbackContext) {
Intent intent = new Intent(this.cordova.getActivity().getApplicationContext(), RedLaserScanner.class);
this.cordova.startActivityForResult((CordovaPlugin) this, intent, 1);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Do something with the result
}
}
RedLaserScanner.java
The RedLaserScanner contains the Android Activity logic and inherits from BarcodeScanActivity (which is a RedLaser SDK class, presumably itself inherits from Activity);
A very simple structure is as follows:
public class RedLaserScanner extends BarcodeScanActivity {
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.preview_overlay_new_portrait);
}
}
I am having trouble because I need to access the project's resources to access R.layout.preview_overlay_new_portrait (which are scatted in the Eclipse project) - but I cannot do this unless I import com.myProject.myApp.R - which makes my plugin have a dependency on the project itself.
I did some investigation and found cordova.getActivity().getResources() which seems useful, but this is not accessible from within my RedLaserScanner - because it does not inherit from CordovaPlugin.
Can somebody please help me with some pointers?
Thanks
I just ran into the same issue and it turns out to be pretty easy to solve. RedLaserScanner extends an activity, so you can just call getResources() like this:
setContentView(getResources("preview_overlay_new_portrait", "layout", getPackageName()));
Hooks can be used to replace source file contents to remove wrong imports and/or add the right imports of resources.
I created a script that do it without needing to specify the files. It tries to find source files (with .java extension), removes any resource import already in it and then put the right resources import (if needed), using the Cordova application package name.
This is the script:
#!/usr/bin/env node
/*
* A hook to add resources class (R.java) import to Android classes which uses it.
*/
function getRegexGroupMatches(string, regex, index) {
index || (index = 1)
var matches = [];
var match;
if (regex.global) {
while (match = regex.exec(string)) {
matches.push(match[index]);
console.log('Match:', match);
}
}
else {
if (match = regex.exec(string)) {
matches.push(match[index]);
}
}
return matches;
}
module.exports = function (ctx) {
// If Android platform is not installed, don't even execute
if (ctx.opts.cordova.platforms.indexOf('android') < 0)
return;
var fs = ctx.requireCordovaModule('fs'),
path = ctx.requireCordovaModule('path'),
Q = ctx.requireCordovaModule('q');
var deferral = Q.defer();
var platformSourcesRoot = path.join(ctx.opts.projectRoot, 'platforms/android/src');
var pluginSourcesRoot = path.join(ctx.opts.plugin.dir, 'src/android');
var androidPluginsData = JSON.parse(fs.readFileSync(path.join(ctx.opts.projectRoot, 'plugins', 'android.json'), 'utf8'));
var appPackage = androidPluginsData.installed_plugins[ctx.opts.plugin.id]['PACKAGE_NAME'];
fs.readdir(pluginSourcesRoot, function (err, files) {
if (err) {
console.error('Error when reading file:', err)
deferral.reject();
return
}
var deferrals = [];
files.filter(function (file) { return path.extname(file) === '.java'; })
.forEach(function (file) {
var deferral = Q.defer();
var filename = path.basename(file);
var file = path.join(pluginSourcesRoot, filename);
fs.readFile(file, 'utf-8', function (err, contents) {
if (err) {
console.error('Error when reading file:', err)
deferral.reject();
return
}
if (contents.match(/[^\.\w]R\./)) {
console.log('Trying to get packages from file:', filename);
var packages = getRegexGroupMatches(contents, /package ([^;]+);/);
for (var p = 0; p < packages.length; p++) {
try {
var package = packages[p];
var sourceFile = path.join(platformSourcesRoot, package.replace(/\./g, '/'), filename)
if (!fs.existsSync(sourceFile))
throw 'Can\'t find file in installed platform directory: "' + sourceFile + '".';
var sourceFileContents = fs.readFileSync(sourceFile, 'utf8');
if (!sourceFileContents)
throw 'Can\'t read file contents.';
var newContents = sourceFileContents
.replace(/(import ([^;]+).R;)/g, '')
.replace(/(package ([^;]+);)/g, '$1 import ' + appPackage + '.R;');
fs.writeFileSync(sourceFile, newContents, 'utf8');
break;
}
catch (ex) {
console.log('Could not add import to "' + filename + '" using package "' + package + '". ' + ex);
}
}
}
});
deferrals.push(deferral.promise);
});
Q.all(deferrals)
.then(function() {
console.log('Done with the hook!');
deferral.resolve();
})
});
return deferral.promise;
}
Just add as an after_plugin_install hook (for Android platform) in your plugin.xml:
<hook type="after_plugin_install" src="scripts/android/addResourcesClassImport.js" />
Hope it helps someone!
I implemented a helper for this to keep things clean. It also helps when you create a plugin which takes config.xml arguments which you store in a string resource file in the plugin.
private int getAppResource(String name, String type) {
return cordova.getActivity().getResources().getIdentifier(name, type, cordova.getActivity().getPackageName());
}
You can use it as follows:
getAppResource("app_name", "string");
That would return the string resource ID for app_name, the actually value still needs to be retrieved by calling:
this.activity.getString(getAppResource("app_name", "string"))
Or for the situation in the original question:
setContentView(getAppResource("preview_overlay_new_portrait", "layout"));
These days I just create a helper which returns the value immediately from the the helper:
private String getStringResource(String name) {
return this.activity.getString(
this.activity.getResources().getIdentifier(
name, "string", this.activity.getPackageName()));
}
which in turn you'd call like this:
this.getStringResource("app_name");
I think it's important to point out that when you have the resource ID you're not always there yet.
try using android.R.layout.preview_overlay_new_portrait
when using Air+AS3 for Android, how do I download a png from aserver?
Also, where do I store it so that it later can be used without downloading again?
Regards
Mirza
Better store your file in ApplicationStoragePath so that you can avoid some security (access denied) problem.
Make sure writeBytesToFile() need to specify file path with file name extension as well (like "images/first.png")
private function download(url:String):void
{
var urlRequest:URLRequest = new URLRequest();
urlRequest.url = url;
var urlStream:URLStream = new URLStream();
urlStream.addEventListener(Event.COMPLETE,onDownload_CompleteHandler);
urlStream.addEventListener(IOErrorEvent.IO_ERROR, onDownload_IOErrorHandler);
urlStream.addEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS,onDownload_ResponseStatusHandler);
urlStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR,onDownload_SecurityHandler);
urlStream.load(urlRequest);
}
protected function onDownload_CompleteHandler(event:Event):void
{
var urlStream:URLStream = event.currentTarget as URLStream;
var outBytes:ByteArray = new ByteArray;
urlStream.readBytes(outBytes,0,urlStream.bytesAvailable);
writeBytesToFile(outBytes);
}
private function writeBytesToFile(outBytes:ByteArray):void
{
var fileToWrite:File = File.applicationStorageDirectory.resolvePath("path/to/store/filenamewithextension");//Where you want store
var fileStream:FileStream = new FileStream();
fileStream.addEventListener(Event.CLOSE, onFileStream_CloseHandler);
fileStream.addEventListener(IOErrorEvent.IO_ERROR,onFileStream_IOErrorHandler);
fileStream.openAsync(fileToWrite, FileMode.WRITE);
fileStream.writeBytes(outBytes, 0, outBytes.length);
fileStream.close();
}
protected function onFileStream_CloseHandler(event:Event = null):void
{
trace("File successfully downloaded ");
}
protected function onFileStream_IOErrorHandler(event:IOErrorEvent):void
{
trace("File can't downloaded file doesn't exist ");
}
protected function onDownload_SecurityHandler(event:SecurityErrorEvent):void
{
logger.error("onDownload_SecurityHandler :: " + event.text);
}
protected function onDownload_ResponseStatusHandler(event:HTTPStatusEvent):void
{
trace("onDownload_ResponseStatusHandler :: " + event.status);
}
protected function onDownload_IOErrorHandler(event:IOErrorEvent):void
{
trace("onDownload_IOErrorHandler :: " + event.text);
}