I've got simple app, and it works fine on my PC. But on my Android-Testing Device it can not work properly. Something wrong with the paths. For Android I'm using the code to get files I want to read/write from StreamingAssets to Application.persistentDataPath:
void Awake(){if (firstWord == "Adroid" || deviceType == "Handheld")
{
StartCoroutine("Downloader");
}
}
IEnumerator Downloader()
{
WWW wwwDD = new WWW("jar:file://" + Application.dataPath + "!/assets/DropDown.xml");
yield return wwwDD;
if (wwwDD.isDone == true)
{
File.WriteAllBytes(Application.persistentDataPath + "/DropDown.xml", wwwDD.bytes);
}
WWW www1st = new WWW("jar:file://" + Application.dataPath + "!/assets/1st.xml");
yield return www1st;
if (www1st.isDone == true)
{
File.WriteAllBytes(Application.persistentDataPath + "/1st.xml", www1st.bytes);
}
WWW wwwDefault = new WWW("jar:file://" + Application.dataPath + "!/assets/Default.xml");
yield return wwwDefault;
if (wwwDefault.isDone == true)
{
File.WriteAllBytes(Application.persistentDataPath + "/Default.xml", wwwDefault.bytes);
}
}
And in logcat I've got error:
UnauthorizedAccessException: Access to the path '/data/data/<my project>/files/' is denied.
Write Access is set to Internal, and if I change it to External and add to AndroidManifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
I get the same problem.
What am I doing wrong? Please tell me, I haven't sleep for about a week trying to solve this
Related
This is Code to Create Folder inside a file in SD card in Android 6.0, Am using Flex Code , Added ANE Extension, AIR version 24.0 and swf = 35. working in debug Mode, But not working Release Mode. Please Help to me.
if(library.Utility.getDeviceType() == "ANDROID")
{
var permissionCheck : File;
permissionCheck = new File(pdfFile.nativePath);
permissionCheck.addEventListener(PermissionEvent.PERMISSION_STATUS , function permissionStatusHandler( e : PermissionEvent ) :void
{
permissionCheck.removeEventListener(PermissionEvent.PERMISSION_STATUS , permissionStatusHandler);
if(e.status == PermissionStatus.GRANTED)
{
// save your file
pdfFolder = File.userDirectory.resolvePath(".Folder");
if(!pdfFolder.exists)
pdfFolder.createDirectory();
trace(pdfFolder.nativePath);
//pdfFile = File.applicationStorageDirectory.resolvePath("pdf/" +file);
var targetFile:File = File.userDirectory.resolvePath(".Folder/" +file);
trace(targetFile.nativePath);
if(targetFile.exists)
targetFile.deleteFile();
if(!targetFile.exists && pdfFile.exists)
{
try
{
pdfFile.copyTo(targetFile);
Template.showFile(targetFile.nativePath);
}
catch(error:Error)
{
Popup.showMessage("PDF Viewer", "Unable to open " + file + ". File does not exists. " + error.message);
trace("Error:" + error.message);
}
}
else
{
Popup.showMessage("PDF Viewer", "Unable to find " + file + ". File does not exists.");
}
}
else
{
//showPermissionError();
trace("Error"+ pdfFolder.nativePath);
}
});
try
{
permissionCheck.requestPermission();
}
catch(error : Error)
{
trace("Error:" + error.message);
}
}
You should try to write your traces (like permission status, pdfFolder.exists and so on) into a textField so you can localize the problem in the release mode.
Also listen to IOErrorEvent.IO_ERROR and SecurityErrorEvent.SECURITY_ERROR on your targetFile and trace them to the textField, maybe there are some problems with that.
Normally you should save the application files in the File.applicationStorageDirectory (and not in the userDirectory), but I guess you want the user to be able to access the files on his own?
Try a folder without the dot ("Folder" instead of ".Folder").
On Android you'll need to grant permissions for writing the files in your application.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
I am new to Unity and Android development, but please do not mark this as a duplicate - I've looked all over the stacks for an answer to this, but similar topics and questions haven't yielded any working solutions, or are wanting on details, or outdated, or seem not to fit my specific need.
Ok, I have a quiz app built from following part 1 & 2 of this tutorial. That link contains all the source files anyone might need for reference, and everything works fine on ios and in the unity editor, fyi.
The trouble is with the loadGameData function from the DataController script in Android. Again, everything works fine in iOS and the unity editor, but when I make an Android sdk, the quiz is blank and the console says the data couldn't be loaded.
Here is how the function is currently written (full file in tutorial link):
private void LoadGameData ()
{
string filePath = Path.Combine (Application.streamingAssetsPath, gameDataFileName);
if (File.Exists (result))
{
string dataAsJson = File.ReadAllText (result);
GameData loadedData = JsonUtility.FromJson<GameData> (dataAsJson);
allRoundData = loadedData.allRoundData;
} // #if(File.Exists...
else
{
Debug.LogError ("Cannot load game data!");
} // #else
} // #LoadGameData
If you check the same tutorial on youtube, you'll see lots of people have noted the same problem with the Android build and have been left unanswered. Same goes with unity forums - that's one reason why I don't think this question is a duplicate and could be helpful to others in a similar situation.
I've found that Android has always been sorta tricky with this and that there used to different ways of accessing a file based on platform, but these days "Application.streamingAssetsPath" should find the streaming assets directory on any platform, even Android.
What I've also learned is that in android, even if the path is correct, the file is compressed and will only return a url. So the url needs to be converted using unity's WWW class. I get that, but as of yet, I haven't been able to re-write my loadGameData function to work properly and load the allRoundData array.
Here's an example of some things I've tried:
IEnumerator androidData()
{
string filePath = Path.Combine (Application.streamingAssetsPath, gameDataFileName);
if (filePath.Contains("://"))
{
WWW www = new WWW(filePath);
yield return www;
result = www.text;
} // #if(filePath.Contains
} // #androidData
private void LoadGameData ()
{
androidData();
if (File.Exists (result))
{
string dataAsJson = File.ReadAllText (result);
GameData loadedData = JsonUtility.FromJson<GameData> (dataAsJson);
allRoundData = loadedData.allRoundData;
} // #if(File.Exists...
else
{
Debug.LogError ("Cannot load game data!");
} // #else
} // #LoadGameData
I know I'm close, and this is probably simple -- but I just can't seem to get to the finish line on this. Can some one help me figure out how to write the loadGameData function so it will load this allRoundData array on android?
An example code would be awesome and much appreciated, not just by me, but I'm sure many others would appreciate it also - Thank you!
UPDATE:
Based on the first answer, I've tested some code that works on the unity editor, but crashes in Android. In the Unity editor, I get the "file already exists" message. Here is the code I've tested:
Already had: private string gameDataFileName = "data.json";
I added the copyFile call above loadGameDate in Start() and wrote the copy file and loadGameData functions like so ..
int copyFileToPersistentDataPath(string gameDataFileName)
{
string persistentPath = Application.persistentDataPath + "/" + gameDataFileName;
try
{
//Copy only if gameDataFileName does not exist
if (!System.IO.File.Exists(persistentPath))
{
string path = System.IO.Path.Combine(Application.streamingAssetsPath, gameDataFileName);
WWW www = new WWW(path);
while (!www.isDone) { }
System.IO.File.WriteAllBytes(persistentPath, www.bytes);
Debug.Log(gameDataFileName + " Successfully Copied File to " + persistentPath);
return 1;
}
else
{
Debug.Log(gameDataFileName + " File already exist here. There is no need to copy it again");
return 0;
}
}
catch (Exception e)
{
Debug.Log(gameDataFileName + " Failed To Copy File. Reason: " + e.Message);
return -1;
}
}
private void LoadGameData ()
{
string tempPath = Path.Combine(Application.persistentDataPath, gameDataFileName);
string dataAsJson = File.ReadAllText(tempPath);
GameData loadedData = JsonUtility.FromJson<GameData>(dataAsJson);
allRoundData = loadedData.allRoundData;
} // #LoadGameData
This works with or without the call to copy the file in the editor, but crashes either way in Android.
I ended up putting the files in a Resources folder and going the resources.load a json file into a text asset and throw that into a string to parse route. I now have two different load functions, one that works in ios etc. and one that works in android. Here is the android function (the resourcesGameDataFile does not have the .json extension):
public void LoadDataForAndroid()
{
TextAsset dataFile = Resources.Load(resourcesGameDataFile) as TextAsset;
string dataAsJson = dataFile.ToString();
GameData loadedData = JsonUtility.FromJson<GameData>(dataAsJson);
allRoundData = loadedData.allRoundData;
Debug.Log ("Android data loaded with" + resourcesGameDataFile);
} // #LoadDataForAndroid
And this works in the unity editor and in Bluestacks (android simulator).
As for loading and saving games, this is a duplicate. I marked and remove this as a duplicate because the answer in the duplicated questions did not explain how to read from the StreamingAssets folder. It only talked about saving and loading data.
Make sure that Write Permission is set to External (SDCard).
The first thing to do is to copy the file from StreamingAssets to the persistentDataPath location.
I've found reading data from Application.streamingAssetsPath problematic but I use two methods to solve this.
int copyFileToPersistentDataPath(string fileNameWithExtensionName)
{
string persistentPath = Application.persistentDataPath + "/" + fileNameWithExtensionName;
try
{
//Copy only if fileNameWithExtensionName does not exist
if (!System.IO.File.Exists(persistentPath))
{
string path = System.IO.Path.Combine(Application.streamingAssetsPath, fileNameWithExtensionName);
WWW www = new WWW(path);
while (!www.isDone) { }
System.IO.File.WriteAllBytes(persistentPath, www.bytes);
Debug.Log(fileNameWithExtensionName + " Successfully Copied File to " + persistentPath);
return 1;
}
else
{
Debug.Log(fileNameWithExtensionName + " File already exist here. There is no need to copy it again");
return 0;
}
}
catch (Exception e)
{
Debug.Log(fileNameWithExtensionName + " Failed To Copy File. Reason: " + e.Message);
return -1;
}
}
If that does not work for you, use the method with WebClient below:
void copyFileToPersistentDataPath(string fileNameWithExtensionName)
{
string path = System.IO.Path.Combine(Application.streamingAssetsPath, fileNameWithExtensionName);
string persistentPath = Application.persistentDataPath + "/" + fileNameWithExtensionName;
Debug.Log("Dir: " + persistentPath);
WebClient webClient = new WebClient();
webClient.Proxy = null;
webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(OnDownloadComplete);
webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(OnUpdateDownloadProgress);
Uri uri = new Uri(path);
webClient.DownloadFileAsync(uri, persistentPath);
}
void OnDownloadComplete(object sender, AsyncCompletedEventArgs e)
{
Debug.Log("Finished Downloading: " + e.Error.Message);
}
void OnUpdateDownloadProgress(object sender, DownloadProgressChangedEventArgs e)
{
Debug.Log("Uploading Progreess: " + e.ProgressPercentage);
}
File Copy Usage:
copyFileToPersistentDataPath("yourFileName.txt");
After copying the file, you can then read and convert it to Json like this:
string fileNameWithExtensionName = "questionfile.txt";
string tempPath = Path.Combine(Application.persistentDataPath, fileNameWithExtensionName);
string dataAsJson = File.ReadAllText(fileNameWithExtensionName);
GameData loadedData = JsonUtility.FromJson<GameData>(dataAsJson);
I've been trying to retrieve directories within Resources folder (Assets -> Resources -> DiscoveryPage -> Bloggers -> various folders I want to get), and it only works on Unity Editor on run time, but not on my Android device.
Here's what I have:
if (Application.platform == RuntimePlatform.Android) {
//discoveryDir = new DirectoryInfo(Application.dataPath + "/Resources/DiscoveryPage/Bloggers");
//discoveryDir = new DirectoryInfo(Application.persistentDataPath + "/Resources/DiscoveryPage/Bloggers");
discoveryDir = new DirectoryInfo("jar:file://" + Application.dataPath + "!/assets" + "/Resources/DiscoveryPage/Bloggers");
MainUI.ShowDebug("Running on Android. #" + discoveryDir.Name);
} else {
discoveryDir = new DirectoryInfo(Application.dataPath + "/Resources/DiscoveryPage/Bloggers");
}
bloggerDirs = discoveryDir.GetDirectories();
if (bloggerDirs == null)
MainUI.ShowDebug("bloggerDirs is null");
else
MainUI.ShowDebug("bloggerDirs is NOT null");
The line:
MainUI.ShowDebug("Running on Android. #" + discoveryDir.Name);
did show up with proper information (I was able to reach Bloggers folder and get the discoveryDir.Name, but
if (bloggerDirs == null)
MainUI.ShowDebug("bloggerDirs is null");
else
MainUI.ShowDebug("bloggerDirs is NOT null");
bloggerDirs doesn't return anything (not even null). Neither debug line was printed out.
Seems like discoveryDir.GetDirectories(); does not work on Android.
Anyone knows the problem and how to get those directories on Android run time?
I'm trying to play some mp3 files (which app downloaded from the remote server) from sd card using Cordova.
I tried to use following:
if($ionicPlatform.is('android')){
src = 'yourPersistantAppFolder/main_expansion/' + src;
}
and
if($ionicPlatform.is('android')){
src = 'sdcard/yourPersistantAppFolder/main_expansion/' + src;
}
But without luck.
I tried to find some anwser here:
https://github.com/apache/cordova-plugin-file
But without the luck.
Many thanks for any advice.
Edit:
Path in Android is displayed like this:
This may help:
(jc??? are mysupport routines)
import { File } from "#ionic-native/file";
import { Diagnostic } from "#ionic-native/diagnostic";
constructor(
...
public file: File,
public diagnostic: Diagnostic
){
this.diagnostic.getExternalSdCardDetails()
.then( (data) => {
this.jcError += "\n" + "sd:" + JSON.stringify( data);
this.jcError += "\n" + "Number cards: " + data.length;
for( let ii = 0; ii < data.length; ii += 1){
let thisElem = data[ii];
if( thisElem.type.toLowerCase() === "application" && thisElem.canWrite){
this.ourSDentry = thisElem;
basePathSD = thisElem.filePath;
break;
}
}
if( !basePathSD){
this.jcError += "\n\n" + "no SD card found";
return;
}
}, (errData)=>{
tag = "getExternalSdCardDetails";
this.jcError += "\n\n" + tag + ":ERR:" + JSON.stringify( errData);
});
I'm implementing a Cordova application (3.2) where I want to use LeafletJS and a map tile provider together with a local filesystem cache of the tiles.
My approach in an overview is the following:
Extend the Leaflet TileLayer
Overwrite the _loadTile method to retrieve the tile either from local filesystem or from remote
My code:
var StorageTileLayer = L.TileLayer.extend({
log: function (text) {
if (this.options.log)
this.options.log(text);
else
console.log("[StorageTileLayer]: " + text);
},
_setUpTile: function (tile, key, value, cache) {
try {
tile._layer = this;
tile.onload = this._tileOnLoad;
tile.onerror = this._tileOnError;
this._adjustTilePoint(tile);
tile.src = value;
this.fire('tileloadstart', {
tile: tile,
url: tile.src
});
this.log("Setting url to " + tile.src);
}
catch (e) {
this.log("ERROR in setUpTile: " + e.message);
}
},
_loadTile: function (tile, tilePoint) {
this._adjustTilePoint(tilePoint);
var key = tilePoint.z + ',' + tilePoint.y + ',' + tilePoint.x;
var self = this;
var tileUrl = self.getTileUrl(tilePoint);
console.log(tileUrl);
console.log(typeof tileUrl);
if (this.options.storage) {
this.log("Load Tile with storage");
this.options.storage.get(key, tileUrl).then(function (value) {
self.log("Tile URL to load: " + value.url);
self._setUpTile(tile, key, value.url, true);
});
} else {
this.log("Load Tile without storage");
self._setUpTile(tile, key, tileUrl, false);
}
}
});
options.storage is a storage which has the method get(key, remoteUrl) and returns either the cached tile from local filestorage (this implementation actual works fine, so here is not the problem) or the remote url but downloads the tile in the background, so that it will be available from local file storage on the next call.
Unfortunately I can see on my device when I use Charles (Web Debugging Proxy) that although the local map tiles are loaded (I can see it from the logs) that there are still a couple of requests to the map tiles provider.
Does anyone have an idea what I am doing wrong and what else I have to overwrite in my StorageTileLayer to prevent the calls to the remote? The real problem is, that the map should work in offline mode as well, but it is not.
Thanks for your help.
Libraries in the environment:
Leaflet (0.7.3)
angularJS (1.2.16)
Cordova (3.2)
I basically fixed it with this code (angular js):
(function (window, L) {
var isDebug = false;
var StorageTileLayer = L.TileLayer.extend({
log: function (text) {
if (!isDebug)
return;
if (this.options.log)
this.options.log(text);
else
console.log("[StorageTileLayer]: " + text);
},
_setUpTile: function (tile, key, value, cache) {
try {
tile._layer = this;
tile.onload = this._tileOnLoad;
tile.onerror = this._tileOnError;
this._adjustTilePoint(tile);
tile.src = value;
this.fire('tileloadstart', {
tile: tile,
url: tile.src
});
}
catch (e) {
this.log("ERROR in setUpTile: " + e.message);
}
},
_loadTile: function (tile, tilePoint) {
this._adjustTilePoint(tilePoint);
var key = tilePoint.z + ',' + tilePoint.y + ',' + tilePoint.x;
var self = this;
var tileUrl = self.getTileUrl(tilePoint);
if (isNaN(tilePoint.x) || isNaN(tilePoint.y)) {
this.log("TilePoint x or y is nan: " + tilePoint.x + "-" + tilePoint.y);
return;
}
if (this.options.storage) {
this.options.storage.get(key, tileUrl).then(function (value) {
self.log("Tile URL to load: " + value.url);
self._setUpTile(tile, key, value.url, true);
});
} else {
this.log("Load Tile without storage");
self._setUpTile(tile, key, tileUrl, false);
}
}
});
window.StorageTileLayer = StorageTileLayer;
})(window, L);
Adding the tile layer to the leaflet map is the important part! you have to prevent the load balancer from getting different urls for each tile. I did it by setting the url of the tole layer to a fixed value:
var url = 'https://a.tiles.mapbox.com/v3/<<YOUR ACCESS CODE>>/{z}/{x}/{y}.png';
var layer = new StorageTileLayer(url, {
storage: TileStorage
});
Of course you still have to implement the TileStorage in my case it has a single method get(key, url) and returns a $q-defer which is resolved with either the local available file. If the file is not available in the local storage it will be downloaded and then the promise is resolved.
Unfortunately this TileStorage is not public available because its an in-house development of my company so I can't share it.
Nevertheless I hope this helps you.