I am trying to upload a new version of my game to my iPhone via Xcode. With this app this has not been a problem before but now I do have a problem.
So I created a new smaller project to test. It works in the editor but not when upload to my iPhone (IOS).
I am running Unity 2021.2.7f1 Apple Silicon.
The problem is related to some missing module so I cannot open the db from IOS.
I think it looks like something with opening the database but I am not sure. The database is copied to the "Application.persistentDataPath" path for sure.
I am running Unity 2021.2.7f1 Apple Silicon.
"dbconn.Open();" is where it fails on IOS but not the Mac. I have not yet tested on Android.
Here is the full code in my test project:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using Mono.Data.Sqlite;
using System.Data;
using System.IO;
using System;
public class TestScript : MonoBehaviour
{
public TMP_Text txt_Output;
private string dbPath;
private string myRecord;
private static string fileName;
private static IDbConnection dbconn;
private static string connection, checkDbPath;
private static IDbCommand dbcmd;
private static string sqlQuery;
private static IDataReader reader;
private int recCounter;
void Start()
{
//dbPath = Application.dataPath + "/StreamingAssets/TestDB.db";
dbPath = InitDBLocation();
txt_Output.text = "NU STARTAR VI!";
DoTheDB();
}
void DoTheDB()
{
connection = "URI=file:" + dbPath; //Path to database.
dbconn = (IDbConnection)new SqliteConnection(connection); //creates database connection
dbconn.Open();
//sqlQuery = "SELECT MyTable, Name, answer2, answer3, answer4, lvl from questions WHERE id = '" + _idNr.ToString() + "'";
sqlQuery = "SELECT Name FROM MyTable;";
dbcmd = dbconn.CreateCommand();
dbcmd.CommandText = sqlQuery;
reader = dbcmd.ExecuteReader();
while (reader.Read())
{
myRecord = reader.GetString(0);
recCounter++;
}
reader.Close();
dbconn.Close();
reader = null;
dbcmd.Dispose();
dbcmd = null;
dbconn = null;
txt_Output.text = "# RECORDS: " + recCounter.ToString();
}
public static string InitDBLocation()
{
fileName = "TestDB.db";
#if UNITY_EDITOR
Debug.Log("\n #if UNITY_EDITOR \n");
return Application.dataPath + "/StreamingAssets/TestDB.db";
#elif UNITY_ANDROID
Debug.Log("\n #elif UNITY_ANDROID \n");
Debug.Log("========= GET ANDROID PATH =========");
PlayerPrefs.SetString("DB ERROR", "");
oldCurrentDbPathVersion = PlayerPrefs.GetInt("currentDbPathVersion");
Debug.Log("oldCurrentDbPathVersion: " + oldCurrentDbPathVersion + " | currentDbPathVersion: " + currentDbPathVersion);
if (File.Exists(Path.Combine(Application.persistentDataPath, fileName)) && oldCurrentDbPathVersion == currentDbPathVersion)
{
Debug.Log("return Path: " + Path.Combine(Application.persistentDataPath, fileName));
return Path.Combine(Application.persistentDataPath, fileName);
}
else
{
Debug.Log("Copy Database");
if (File.Exists(Path.Combine(Application.persistentDataPath, fileName)))
{
File.Delete(Path.Combine(Application.persistentDataPath, fileName));
}
PlayerPrefs.SetInt("currentDbPathVersion", currentDbPathVersion);
// °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°° //
try
{
var loadingRequest = UnityWebRequest.Get(Path.Combine(Application.streamingAssetsPath, fileName));
loadingRequest.SendWebRequest();
while (!loadingRequest.isDone)
{
if (loadingRequest.isNetworkError || loadingRequest.isHttpError)
{
break;
}
}
Debug.Log("Write database to persistent");
File.WriteAllBytes(Path.Combine(Application.persistentDataPath, fileName), loadingRequest.downloadHandler.data);
Debug.Log("return Path: " + Path.Combine(Application.persistentDataPath, fileName));
return Path.Combine(Application.persistentDataPath, fileName);
}
catch (Exception e)
{
PlayerPrefs.SetString("DB ERROR", e.ToString());
PlayerPrefs.SetString("dbPath", "");
Debug.LogError("*** FILECOPY ERROR ***\n\n" + e); //" + exception.Message + "\n" + exception.StackTrace);
return null;
}
Debug.Log("========= END ANDROID dbPath=========");
// °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°° //
}
#elif UNITY_IOS
Debug.Log("\n #elif UNITY_IOS \n");
Debug.Log("TEST GENERAL: " + Application.streamingAssetsPath);
// ========= IOS ========= //
Debug.Log("========= IOS DB PATH =========");
if (File.Exists(Path.Combine(Application.persistentDataPath, fileName)))
{
Debug.Log("\n>>>> DB File.Exists <<<<<<\n");
return Path.Combine(Application.persistentDataPath, fileName);
}
else
{
if (File.Exists(Path.Combine(Application.persistentDataPath, fileName)))
{
File.Delete(Path.Combine(Application.persistentDataPath, fileName));
}
// °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°° //
try
{
System.IO.File.Copy((Path.Combine(UnityEngine.Application.streamingAssetsPath, fileName)), Path.Combine(Application.persistentDataPath, fileName), true);
Debug.Log("\n return Path: " + Path.Combine(Application.persistentDataPath, fileName));
Debug.Log("\n To PATH: " + Application.persistentDataPath + "\" + fileName);
return Path.Combine(Application.persistentDataPath, fileName);
}
catch (Exception e)
{
Debug.LogError("*** FILECOPY ERROR ***\n\n" + e); //" + exception.Message + "\n" + exception.StackTrace);
PlayerPrefs.SetString("DB ERROR", e.ToString());
PlayerPrefs.SetString("dbPath", "");
return null;
}
Debug.Log("========= END IOS dbPath=========");
// °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°° //
}
#endif
}
}
Here is the Xcode log:
2022-01-08 22:41:04.039955+0100 NewSQLitetest[2350:900169] Built from '2021.2/staging' branch, Version '2021.2.7f1 (6bd9e232123f)', Build type 'Release', Scripting Backend 'il2cpp'
2022-01-08 22:41:04.040632+0100 NewSQLitetest[2350:900169] MemoryManager: Using 'Default' Allocator.
[UnityMemory] Configuration Parameters - Can be set up in boot.config
"memorysetup-bucket-allocator-granularity=16"
"memorysetup-bucket-allocator-bucket-count=8"
"memorysetup-bucket-allocator-block-size=4194304"
"memorysetup-bucket-allocator-block-count=1"
"memorysetup-main-allocator-block-size=16777216"
"memorysetup-thread-allocator-block-size=16777216"
"memorysetup-gfx-main-allocator-block-size=16777216"
"memorysetup-gfx-thread-allocator-block-size=16777216"
"memorysetup-cache-allocator-block-size=4194304"
"memorysetup-typetree-allocator-block-size=2097152"
"memorysetup-profiler-bucket-allocator-granularity=16"
"memorysetup-profiler-bucket-allocator-bucket-count=8"
"memorysetup-profiler-bucket-allocator-block-size=4194304"
"memorysetup-profiler-bucket-allocator-block-count=1"
"memorysetup-profiler-allocator-block-size=16777216"
"memorysetup-profiler-editor-allocator-block-size=1048576"
"memorysetup-temp-allocator-size-main=4194304"
"memorysetup-job-temp-allocator-block-size=2097152"
"memorysetup-job-temp-allocator-block-size-background=1048576"
"memorysetup-job-temp-allocator-reduction-small-platforms=262144"
"memorysetup-temp-allocator-size-background-worker=32768"
"memorysetup-temp-allocator-size-job-worker=262144"
"memorysetup-temp-allocator-size-preload-manager=262144"
"memorysetup-temp-allocator-size-nav-mesh-worker=65536"
"memorysetup-temp-allocator-size-audio-worker=65536"
"memorysetup-temp-allocator-size-cloud-worker=32768"
"memorysetup-temp-allocator-size-gfx=262144"
-> applicationDidFinishLaunching()
-> applicationDidBecomeActive()
GfxDevice: creating device client; threaded=1; jobified=1
Initializing Metal device caps: Apple A13 GPU
Initialize engine version: 2021.2.7f1 (6bd9e232123f)
2022-01-08 22:41:04.410523+0100 NewSQLitetest[2350:900465] fopen failed for data file: errno = 2 (No such file or directory)
2022-01-08 22:41:04.410600+0100 NewSQLitetest[2350:900465] Errors found! Invalidating cache...
2022-01-08 22:41:04.831038+0100 NewSQLitetest[2350:900465] fopen failed for data file: errno = 2 (No such file or directory)
2022-01-08 22:41:04.831115+0100 NewSQLitetest[2350:900465] Errors found! Invalidating cache...
2022-01-08 22:41:05.052099+0100 NewSQLitetest[2350:900169] Unbalanced calls to begin/end appearance transitions for <UnityDefaultViewController: 0x103429f00>.
UnloadTime: 1.043667 ms
#elif UNITY_IOS
TestScript:InitDBLocation()
TestScript:Start()
TEST GENERAL: /private/var/containers/Bundle/Application/2FE4741C-1580-4698-9BC1-9512D578CFF6/NewSQLitetest.app/Data/Raw
TestScript:InitDBLocation()
TestScript:Start()
========= IOS DB PATH =========
TestScript:InitDBLocation()
TestScript:Start()
return Path: /var/mobile/Containers/Data/Application/F3747560-C7AD-4FBC-A1C6-F95843F09906/Documents/TestDB.db
TestScript:InitDBLocation()
TestScript:Start()
To PATH: /var/mobile/Containers/Data/Application/F3747560-C7AD-4FBC-A1C6-F95843F09906/DocumentsTestDB.db
TestScript:InitDBLocation()
TestScript:Start()
MissingMethodException: System.Runtime.InteropServices.Marshal::SetLastWin32Error(System.Int32)
at System.Runtime.InteropServices.CriticalHandle.Cleanup () [0x00000] in <00000000000000000000000000000000>:0
at Mono.Data.Sqlite.SqliteStatement.Dispose () [0x00000] in <00000000000000000000000000000000>:0
at Mono.Data.Sqlite.SqliteCommand.ClearCommands () [0x00000] in <00000000000000000000000000000000>:0
at Mono.Data.Sqlite.SqliteCommand.set_CommandText (System.String value) [0x00000] in <00000000000000000000000000000000>:0
at Mono.Data.Sqlite.SqliteConnection.Open () [0x00000] in <00000000000000000000000000000000>:0
at TestScript.DoTheDB () [0x00000] in <00000000000000000000000000000000>:0
I finally solved this problem based on the above post, see thread in my comment. Comment from Baydogan:
Finally I have found a workaround until unity fixes this bug.
Don't modify any unity editor file. Just build for iOS as usual.
Search for "// System.Void System.Runtime.InteropServices.CriticalHandle::Cleanup()" on xcode project.
Move a few lines below and comment out the line that starts with : "Marshal_SetLastWin32Error_"
DONE
example:
IL_002b:
{ il2cpp_codegen_runtime_class_init_inline(Marshal_tD976A56A90263C3CE2B780D4B1CADADE2E70B4A7_il2cpp_TypeInfo_var);
// Marshal_SetLastWin32Error_mC871D8DAC36418FAA95AFD23CD2A3ECC94628D1C(G_B6_0, NULL); il2cpp_codegen_runtime_class_init_inline(GC_t920F9CF6EBB7C787E5010A4352E1B587F356DC58_il2cpp_TypeInfo_var);
GC_SuppressFinalize_m3352E2F2119EB46913B51B7AAE2F217C63C35F2A(__this, NULL);
return;
}
Related
Can anyone help me to resolve my healthKit capability issue for a unity app.
I am trying to add healthKit capability to my unity app. I am using BEHEALTHKIT and HealthKitBuildProcessor.cs editor class to add capability and other dependencies. Following are the code I am using .But for some reason healthkit Capability and entitlements are not adding through this code (permission parameters are adding to plist), and returning null when I print Debug.Log("newEntitlements: " + newEntitlements);Also my build failing with an error saying "provisioning profile doesn't support the HealthKit Capability"
I have already added HealthKit capability for the profile from developer.apple.com.
Unity version: 2019.4.4f1
public class HealthKitBuildProcessor : IProcessSceneWithReport
{
private static string shareString = null;
private static string updateString = null;
private static string clinicalString = null;
/*! #brief required by the IProcessScene interface. Set high to let other postprocess scripts run first. */
public int callbackOrder {
get { return 100; }
}
/*! #brief Searches for HealthKitDataTypes objects & reads the usage strings for the OnPostprocessBuild phase.
#param scene the scene being processed.
#param report a report containing information about the current build
*/
public void OnProcessScene(Scene scene, BuildReport report) {
GameObject[] rootObjects = scene.GetRootGameObjects();
foreach (GameObject obj in rootObjects) {
HealthKitDataTypes types = obj.GetComponentInChildren<HealthKitDataTypes>();
if (types != null) {
if (types.AskForSharePermission()) {
HealthKitBuildProcessor.shareString = types.healthShareUsageDescription;
}
if (types.AskForUpdatePermission()) {
HealthKitBuildProcessor.updateString = types.healthUpdateUsageDescription;
}
/*if (types.AskForClinicalPermission()) {
HealthKitBuildProcessor.clinicalString = types.clinicalUsageDescription;
}*/
}
}
}
/*! #brief Updates the Xcode project.
#param buildTarget the target build platform
#param path the path of the target build
*/
[PostProcessBuildAttribute(10)]
public static void OnPostprocessBuild(BuildTarget buildTarget, string path) {
Debug.Log("--- BEHEALTHKIT POST-PROCESS BUILD ---");
if (buildTarget == BuildTarget.iOS) {
//string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
//Debug.Log("BE:PROJECT PATH :" + projPath);
var projPath = PBXProject.GetPBXProjectPath(path);
var proj = new PBXProject();
proj.ReadFromString(System.IO.File.ReadAllText(projPath));
#if UNITY_2019_3_OR_NEWER
string mainTarget = proj.GetUnityMainTargetGuid();
string frameworkTarget = proj.GetUnityFrameworkTargetGuid();
Debug.Log("--- BE: UNITY_2019_3_OR_NEWER ---");
Debug.LogFormat("main target: {0}", mainTarget);
Debug.LogFormat("framework target: {0}", frameworkTarget);
#else
string targetName = PBXProject.GetUnityTargetName();
string mainTarget = proj.TargetGuidByName(targetName);
Debug.Log("---BE: ELSE UNITY_2019_3_OR_NEWER ---");
Debug.Log("main target: {0}", mainTarget);
Debug.Log("targetName: ", targetName);
#endif
bool addHealthRecordsCapability = (clinicalString != null);
//Debug.Log("addHealthRecordsCapability: ", addHealthRecordsCapability);
// Info.plist
//-----------
Debug.Log("---BE: PLIST ---");
var info = ProcessInfoPList(path, addHealthRecordsCapability);
// Entitlements
//--------------
Debug.Log("---BE: ProcessEntitlements ---");
string entitlementsRelative = ProcessEntitlements(path, proj, mainTarget, info, addHealthRecordsCapability);
#if UNITY_2019_3_OR_NEWER
// add HealthKit capability
Debug.Log("------projPath "+projPath);
ProjectCapabilityManager capabilities = new ProjectCapabilityManager(projPath, "Entitlements.entitlements", null, mainTarget);
capabilities.AddHealthKit();
Debug.Log("---BE:Capability UNITY_2019_3_OR_NEWER ---");
// add HealthKit Framework
//proj.AddFrameworkToProject(frameworkTarget, "HealthKit.framework", true);
// Set a custom link flag
//proj.AddBuildProperty(frameworkTarget, "OTHER_LDFLAGS", "-ObjC");
#else
// add HealthKit capability
Debug.Log("---ELSE BE:Capability UNITY_2019_3_OR_NEWER ---");
Debug.Log("projectPath:" + projPath);
Debug.Log("entitlementsRelative:" + entitlementsRelative);
Debug.Log("targetName:" + targetName);
ProjectCapabilityManager capabilities = new ProjectCapabilityManager(projPath, entitlementsRelative, targetName);
capabilities.AddHealthKit();
// add HealthKit Framework
proj.AddFrameworkToProject(mainTarget, "HealthKit.framework", true);
// Set a custom link flag
proj.AddBuildProperty(mainTarget, "OTHER_LDFLAGS", "-ObjC");
#endif
proj.WriteToFile(projPath);
}
}
// -------------------------------
internal static PlistDocument ProcessInfoPList(string path, bool addHealthRecordsCapability) {
string plistPath = Path.Combine(path, "Info.plist");
PlistDocument info = GetInfoPlist(plistPath);
PlistElementDict rootDict = info.root;
// // Add the keys
if (HealthKitBuildProcessor.shareString != null) {
rootDict.SetString("NSHealthShareUsageDescription", HealthKitBuildProcessor.shareString);
}
else {
Debug.LogError("unable to read NSHealthShareUsageDescription");
}
if (HealthKitBuildProcessor.updateString != null) {
rootDict.SetString("NSHealthUpdateUsageDescription", HealthKitBuildProcessor.updateString);
}
if (addHealthRecordsCapability) {
rootDict.SetString("NSHealthClinicalHealthRecordsShareUsageDescription", HealthKitBuildProcessor.clinicalString);
}
// Write the file
info.WriteToFile(plistPath);
return info;
}
internal static string ProcessEntitlements(string path, PBXProject proj, string target, PlistDocument info, bool addHealthRecordsCapability) {
string entitlementsFile;
string entitlementsRelative;
string entitlementsPath;
Debug.Log("PATH: " + path);
Debug.Log("TARGET: " + target);
String test= proj.GetUnityMainTargetGuid();
Debug.Log("TEST proj: " + test);
entitlementsRelative = proj.GetBuildPropertyForConfig(target, "CODE_SIGN_ENTITLEMENTS");
Debug.Log("entitlementsRelative: " + entitlementsRelative);
Debug.LogFormat("get build property [{0}, {1} = {2}]", target, "CODE_SIGN_ENTITLEMENTS", entitlementsRelative);
PlistDocument entitlements = new PlistDocument();
if (entitlementsRelative == null) {
string projectname = GetProjectName(info);
Debug.Log("projectname: " + projectname);
entitlementsFile = Path.ChangeExtension("Entitlements", "entitlements");
Debug.Log("entitlementsFile: " + entitlementsFile);
entitlementsRelative = Path.Combine(path, entitlementsFile);
Debug.Log("entitlementsRelative: " + entitlementsRelative);
entitlementsPath = Path.Combine(path, entitlementsRelative);
Debug.Log("entitlementsPath: " + entitlementsPath);
//proj.AddFileToBuild(target, proj.AddFile(entitlementsRelative, entitlementsRelative, PBXSourceTree.Source));
Debug.LogFormat("add build property [{0}, {1}] => {2}", target, "CODE_SIGN_ENTITLEMENTS", entitlementsRelative);
proj.AddBuildProperty(target, "CODE_SIGN_ENTITLEMENTS", entitlementsFile);
string newEntitlements = proj.GetBuildPropertyForConfig(target, "CODE_SIGN_ENTITLEMENTS");
Debug.Log("newEntitlements: " + newEntitlements);
Debug.LogFormat("=> {0}", newEntitlements);
}
else {
entitlementsPath = Path.Combine(path, entitlementsRelative);
Debug.Log("ELSE:entitlementsPath " + entitlementsPath);
}
ReadEntitlements(entitlements, entitlementsPath);
entitlements.root.SetBoolean("com.apple.developer.healthkit", true);
if (addHealthRecordsCapability) {
Debug.Log("addHealthRecordsCapability =TRUE ");
var healthkitAccess = entitlements.root.CreateArray("com.apple.developer.healthkit.access");
healthkitAccess.AddString("health-records");
}
SaveEntitlements(entitlements, entitlementsPath);
return entitlementsRelative;
}
// -------------------------------
internal static void ReadEntitlements(PlistDocument entitlements, string destinationPath) {
Debug.Log("READING Entitlements [ReadEntitlements]");
Debug.Log("READING from destinationPath [ReadEntitlements]"+ destinationPath);
if (System.IO.File.Exists(destinationPath)) {
try {
Debug.LogFormat("reading existing entitlements: '{0}'.", destinationPath);
entitlements.ReadFromFile(destinationPath);
}
catch (Exception e) {
Debug.LogErrorFormat("error reading from file: {0}", e);
}
}
}
internal static void SaveEntitlements(PlistDocument entitlements, string destinationPath) {
try {
Debug.Log("----SaveEntitlements---");
entitlements.WriteToFile(destinationPath);
}
catch (Exception e) {
Debug.LogErrorFormat("error writing to file: {0}", e);
}
}
internal static PlistDocument GetInfoPlist(string plistPath) {
// Get the plist file
PlistDocument plist = new PlistDocument();
plist.ReadFromFile(plistPath);
return plist;
}
internal static string GetProjectName(PlistDocument plist) {
string projectname = plist.root["CFBundleDisplayName"].AsString();
return projectname;
}
}
I don't know about Unity, but from Xcode when you add a capability for health kit it will update the entitlement file by itself
I want to have some of my files in my assets folder to be copied to the android device. How do you do it?
I have tried several ways, like opening the files via debugging, it works in the PC. But when I have transferred it to the android device, it doesn't work or either copy it whatnot.
public void OpenPDF(string filename) //this opens the file I have in my pc. Via debugging in Unity.
{
TextAsset pdfTem = Resources.Load("PDFs/" + filename, typeof(TextAsset)) as TextAsset;
System.IO.File.WriteAllBytes(Application.persistentDataPath + "/" + filename + ".pdf", pdfTem.bytes);
Application.OpenURL(Application.persistentDataPath + "/" + filename + ".pdf");
}
public void openPDFfromSD()
{
Application.OpenURL("/mnt/sdcard/openme.pdf"); //this doesn't open the PDF file I have in my sd card.
}
public void legitOpen(string nameOfFile) //this opens the file I have in my pc. Via debugging in Unity.
{
string realPath = Application.persistentDataPath + "/" + nameOfFile + ".pdf";
if (!System.IO.File.Exists(realPath))
{
if (!System.IO.Directory.Exists(Application.persistentDataPath + "/PDFs/"))
{
System.IO.Directory.CreateDirectory(Application.persistentDataPath + "/PDFs/");
}
WWW reader = new WWW(Application.streamingAssetsPath + "/PDFs/" + realPath);
while (!reader.isDone) { }
System.IO.File.WriteAllBytes(realPath, reader.bytes);
}
Application.OpenURL(realPath);
}
In general you shouldn't use string concatenation directly to build system paths.
Rather always use Path.Combine which automatically uses the correct path separator (/ or \) according to your target platform.
Also later in new WWW you have added both the leading Application.streamingAssetsPath and Application.persistentDataPath already in realPath.
public void OpenPDF(string filename)
{
TextAsset pdfTem = Resources.Load("PDFs/" + filename, typeof(TextAsset)) as TextAsset;
var filePath = Path.Combine(Application.persistentDataPath, filename + ".pdf";
System.IO.File.WriteAllBytes(filePath), pdfTem.bytes);
Application.OpenURL(filePath);
}
public void legitOpen(string nameOfFile)
{
string realPath = Path.Combine(Application.persistentDataPath, nameOfFile + ".pdf");
if (!System.IO.File.Exists(realPath))
{
if (!System.IO.Directory.Exists(Path.Combine(Application.persistentDataPath, "PDFs"))
{
System.IO.Directory.CreateDirectory(Path.Combine(Application.persistentDataPath, "PDFs"));
}
WWW reader = new WWW(Path.Combine(Application.streamingAssetsPath, "PDFs", nameOfFile + ".pdf");
while (!reader.isDone) { }
System.IO.File.WriteAllBytes(realPath, reader.bytes);
}
Application.OpenURL(realPath);
}
Btw if you want to prevent your app from completely freezing until the loading is done I would recommend to use a Coroutine and UnityWebRequest.Get like
public void legitOpen(string nameOfFile)
{
StartCoroutine(legitOpenRoutine(nameOfFile));
}
private IEnumerator legitOpenRoutine(string nameOfFile)
{
string realPath = Path.Combine(Application.persistentDataPath, nameOfFile + ".pdf");
if (!System.IO.File.Exists(realPath))
{
if (!System.IO.Directory.Exists(Path.Combine(Application.persistentDataPath, "PDFs"))
{
System.IO.Directory.CreateDirectory(Path.Combine(Application.persistentDataPath, "PDFs"));
}
using (var reader = new UnityWebRequest.Get(Path.Combine(Application.streamingAssetsPath, "PDFs", nameOfFile + ".pdf"))
{
yield return reader.SendWebRequest();
if (webRequest.isNetworkError)
{
Debug.Log(pages[page] + ": Error: " + webRequest.error);
return;
}
System.IO.File.WriteAllBytes(realPath, reader.bytes);
}
}
Application.OpenURL(realPath);
}
Or even completely use an async method using CopyToAsync
public void legitOpen(string nameOfFile)
{
legitOpenAsync(nameOfFile);
}
private async void legitOpenAsync(string nameOfFile)
{
var realPath = Path.Combine(Application.persistentDataPath, nameOfFile + ".pdf");
var pdfPath = Path.Combine(Application.persistentDataPath, "PDFs");
if (!System.IO.File.Exists(realPath))
{
if (!System.IO.Directory.Exists(pdfPath)
{
System.IO.Directory.CreateDirectory(pdfPath);
}
using(var sourceFile = File.Open(Path.Combine(Application.streamingAssetsPath, "PDFs", nameOfFile + ".pdf"), FileMode.Open, FileAccess.Read, FileShare.Read)
{
using(var targetFile = File.Open(realPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write))
{
await sourceFile.CopyToAsync(targetFile);
}
}
}
Application.OpenURL(realPath);
}
However
note that Application.OpenURL:
Android: Due security changes in Android 7.0 (More information), Application.OpenURL can no longer be used for opening local app files, you need to use FileProvider which allows you to share files with other applications.
and
iOS: Application.OpenURL cannot be used for opening local files.
Therefore this won't work at all
public void openPDFfromSD()
{
Application.OpenURL("/mnt/sdcard/openme.pdf");
}
Im trying to implement a local database (sqlite) in an Android/PC game
I am able to load the database from the editor and the standalone pc build of the games but the android version has an error that does not find the tables im trying to query from
This is the code that loads the database into three lists from the streaming assets folders
using System;
using System.IO;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using Mono.Data.Sqlite;
using UnityEngine;
using UnityEngine.SceneManagement;
public class DBAdministrator : MonoBehaviour {
//
//Android sdk > platform-tools > adb logcat MarsGames.ChippyRemix *:E
public List<LevelDP> levelsList;
public List<LevelDP> defaultLevels;
public List<LevelDP> myLevels;
public List<LevelDP> otherLevels;
GameObject playMenu;
public string myName;
public string defaultName = "chippy";
public string debugString;
void Start () {
//myName = PlayerPrefs.GetString("MyName");
playMenu = GameObject.FindWithTag("UI");
//levelsList = new List<LevelDP>();
defaultLevels = new List<LevelDP>();
myLevels = new List<LevelDP>();
otherLevels = new List<LevelDP>();
//Fill the list
FillLists();
//PrintAllLevels();
if(playMenu != null){
playMenu.SendMessage("CreateDisplayLists");
}
}
void FillLists(){
//Path to database
string conn = "";
//Debug.Log(Application.streamingAssetsPath);
#if UNITY_EDITOR_WIN
//Debug.Log("Using unity editor conn");
conn = "URI=file:" + Application.dataPath + "/StreamingAssets/chickdb.db";
#elif UNITY_ANDROID
debugString = "Using andriod";
conn = Application.persistentDataPath + "/chickdb.db";
if(!File.Exists(conn)){
debugString = "DB file does not exist";
//Open Streaming assets and load the db
WWW loadDb = new WWW("jar:file://" + Application.dataPath + "!/assets/chickdb.db");
while (!loadDb.isDone) { }
File.WriteAllBytes(conn,loadDb.bytes);
}else{
debugString = "File exists";
//debugString = conn;
}
//Once file is loaded, use the appropiate filepath to access db
conn = "URI=file:" + Application.persistentDataPath + "/chickdb.db";
#elif UNITY_STANDALONE
//debugString = "Using standalone PC";
//string conn = "URI=file:" + System.IO.Path.Combine(Application.streamingAssetsPath, "Database/TMDB.s3db");
//conn = "URI=File:" + System.IO.Path.Combine(Application.streamingAssetsPath,"/StreamingAssets/chickdb.db");
conn = "URI=file:" + Application.streamingAssetsPath + "/chickdb.db";
debugString = conn;
/*
#elif UNITY_IOS
debugString = "Using ios";
// this is the path to your StreamingAssets in iOS
var loadDb = Application.dataPath + "/Raw/chickdb.do";
// then save to Application.persistentDataPath
File.Copy(loadDb, filepath);
//conn = "URI=file " + Application.persistentDataPath + "/chickdb.db";
*/
#endif
//Debug.Log("ST Connection DB: " + conn);
#if UNITY_ANDROID
debugString = "Connection Attempted";
#endif
IDbConnection dbconn = (IDbConnection) new SqliteConnection(conn);
//Open connection to the database.
dbconn.Open();
#if UNITY_ANDROID
debugString = "Database Open";
#endif
IDbCommand dbcmd = dbconn.CreateCommand();
string sqlQuery = "SELECT * FROM levels";
dbcmd.CommandText = sqlQuery;
IDataReader reader = dbcmd.ExecuteReader();
#if UNITY_ANDROID
debugString = "Query executed";
#endif
while (reader.Read()){
LevelDP lp = new LevelDP();
lp.SetLevelID(reader.GetInt32(0));
lp.SetLevelName(reader.GetString(1));
lp.SetCreationDate(reader.GetString(2));
lp.SetUserID(reader.GetString(3));
lp.SetBlocks(reader.GetString(4));
lp.SetCompleted(reader.GetInt32(5));
//levelsList.Add(lp);
if(lp.GetUserID().Equals(defaultName)){
defaultLevels.Add(lp);
}else if(lp.GetUserID().Equals(myName)){
myLevels.Add(lp);
}else{
otherLevels.Add(lp);
}
}
//Close the db reader
reader.Close();
reader = null;
//Close the comand executer
dbcmd.Dispose();
dbcmd = null;
//Close the db connection
dbconn.Close();
dbconn = null;
}
void PrintAllLevels(){
Debug.Log("Print all levels");
for(int i = 0; i < defaultLevels.Count; i++){
Debug.Log(defaultLevels[i].ToString());
}
for(int i = 0; i < myLevels.Count; i++){
Debug.Log(myLevels[i].ToString());
}
for(int i = 0; i < otherLevels.Count; i++){
Debug.Log(otherLevels[i].ToString());
}
}
}
From this code i've been able to print ("Database Open") but when the database is supposed to query i get an error (from the adb tool) that states ("Table levels not found") and i dont know where the error is
I do have to add the editor, and pc build work just fine,
Steps i've followed are:
Place the .db file in the Streaming Assets folder Unpack the .apk and check the values present in the .db file via sqlitebrowser
i have a theory that maybe im not loading the db correctly into memory in the #if UNITY_ANDROID conditional statement but almost every other example ive seen works this way
I can't comment, that's why Im writing an answer.
Try to use Debug.Log to see the pathfile when you are testing the game/app.
And then checking if your sq3db file is in the correct folder.
I Have a similar code for streaming the sqlite DB but if I test it on PC the DB should be in Assets folder not in StreamingAssets folder.
Edit 1:
First:
I'll show you a fragment of the code I use when I make games:
if(Application.platform != RuntimePlatform.Android)
_filepath = Application.dataPath + "/" + _DBName;
else
{
_filepath = Application.persistentDataPath + "/" + _DBName;
if (!File.Exists(_filepath))
{
//Debug.Log("Check in");
WWW loadDB = new WWW("jar:file://" + Application.dataPath +
"!/assets/" + _DBName);
while (!loadDB.isDone) { }
File.WriteAllBytes(_filepath, loadDB.bytes);
}
}
_StringConnection = "URI=file:" + _filepath;
Second:
When I test the game on pc (unity editor) I have my Database (.s3db) here:
ProjectName/Assets/Database.s3db
When I run the game on android I have my Database here:
ProjectName/Assets/StreamingAssets/Database.s3db
Something you may notice is that my Database file extension is s3db, I use SQlite3, but it should work anyway.
In Android sqlite db should save in persistent data path. I put my sqlite db in resources with .txt extension and read it as text. then chagne the extension in code and save it in Application.persistentDataPath.
When I run my game from the editor and save load data, I find that data in: C:\Users\User\AppData\LocalLow\DefaultCompany\projectname\data.
When I build it and get an executable, that data still loads fine, but if I save and restart, it does not get saved. However it does when I launch it from the Editor.
Is this a fault in my code or is the files somewhere else when I don't run it from Unity Editor?
Later when I export the game to launch it, I have persistent data I Json files that have to come with it for the game to work.
For clarity, here is the class that handles save/load of Json:
public class DataHandler
{
//Save Data
public static void saveData<T>(T dataToSave, string dataFileName)
{
string tempPath = Path.Combine(Application.persistentDataPath, "data");
tempPath = Path.Combine(tempPath, dataFileName + ".txt");
//Convert To Json then to bytes
string jsonData = JsonUtility.ToJson(dataToSave, true);
byte[] jsonByte = Encoding.ASCII.GetBytes(jsonData);
//Create Directory if it does not exist
if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
{
Directory.CreateDirectory(Path.GetDirectoryName(tempPath));
}
//Debug.Log(path);
try
{
File.WriteAllBytes(tempPath, jsonByte);
Debug.Log("Saved Data to: " + tempPath.Replace("/", "\\"));
}
catch (Exception e)
{
Debug.LogWarning("Failed To PlayerInfo Data to: " + tempPath.Replace("/", "\\"));
Debug.LogWarning("Error: " + e.Message);
}
}
//Load Data
public static T loadData<T>(string dataFileName)
{
string tempPath = Path.Combine(Application.persistentDataPath, "data");
tempPath = Path.Combine(tempPath, dataFileName + ".txt");
//Exit if Directory or File does not exist
if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
{
Debug.LogWarning("Directory does not exist");
return default(T);
}
if (!File.Exists(tempPath))
{
Debug.Log("File does not exist");
return default(T);
}
//Load saved Json
byte[] jsonByte = null;
try
{
jsonByte = File.ReadAllBytes(tempPath);
Debug.Log("Loaded Data from: " + tempPath.Replace("/", "\\"));
}
catch (Exception e)
{
Debug.LogWarning("Failed To Load Data from: " + tempPath.Replace("/", "\\"));
Debug.LogWarning("Error: " + e.Message);
}
//Convert to json string
string jsonData = Encoding.ASCII.GetString(jsonByte);
//Convert to Object
object resultValue = JsonUtility.FromJson<T>(jsonData);
return (T)Convert.ChangeType(resultValue, typeof(T));
}
public static bool deleteData(string dataFileName)
{
bool success = false;
//Load Data
string tempPath = Path.Combine(Application.persistentDataPath, "data");
tempPath = Path.Combine(tempPath, dataFileName + ".txt");
//Exit if Directory or File does not exist
if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
{
Debug.LogWarning("Directory does not exist");
return false;
}
if (!File.Exists(tempPath))
{
Debug.Log("File does not exist");
return false;
}
try
{
File.Delete(tempPath);
Debug.Log("Data deleted from: " + tempPath.Replace("/", "\\"));
success = true;
}
catch (Exception e)
{
Debug.LogWarning("Failed To Delete Data: " + e.Message);
}
return success;
}
}
In the answer below:
companyname = Company name from the Build Settings
productname = Product name from the Build Settings
Windows:
C:\Users\<userprofile>\AppData\LocalLow\<companyname>\<productname>
Windows Store:
%userprofile%\AppData\Local\Packages\<productname>\LocalState
Mac:
~/Library/Application Support/companyname/productname
older version of Unity on Mac:
~/Library/Caches folder
~/Library/Application Support/unity.companyname.productname.
Linux:
$XDG_CONFIG_HOME/unity3d/<companyname>/<productname>
which is the-same as
~/.config/unity3d/<companyname>/<productname>
Android:
/Data/Data/com.<companyname>.<productname>/files
with SD card on the Android device:
/storage/sdcard0/Android/data/com.<companyname>.<productname>/files
iOS:
/var/mobile/Containers/Data/Application/<RandomFolderName>/Documents
Example of the RandomFolderName full name:
/var/mobile/Containers/Data/Application/<055811B9-D125-41B1-A078-F898B06F8C58>/Documents
On iOS, you will be given access to the app's sandbox which is the Document folder. You must create folder inside this directory in order to create a new file inside it.
If you have a default data values in a json file, put the file in Assets/Resources folder so that it will be read-only then read it with TextAsset. When the game loads, you can use PlayerPrefs to check if this is the first time the game being loaded.
If this is the first time, use the value from TextAsset.text. If it is not use then value saved with the DataHandler class.
Roughly something like this:
if (PlayerPrefs.GetInt("FIRSTTIMEOPENING", 1) == 1)
{
Debug.Log("First Time Opening");
//Set first time opening to false
PlayerPrefs.SetInt("FIRSTTIMEOPENING", 0);
//USE TextAsset to load data
TextAsset txtAsset = (TextAsset)Resources.Load("player", typeof(TextAsset));
string tileFile = txtAsset.text;
PlayerInfo pInfo = JsonUtility.FromJson<PlayerInfo>(tileFile);
}
else
{
Debug.Log("NOT First Time Opening");
//USE DataHandler to load data
PlayerInfo pInfo = DataHandler.loadData<PlayerInfo>("player");
}
When I run my game from the editor and save load data, I find that data in: C:\Users\User\AppData\LocalLow\DefaultCompany\projectname\data.
When I build it and get an executable, that data still loads fine, but if I save and restart, it does not get saved. However it does when I launch it from the Editor.
Is this a fault in my code or is the files somewhere else when I don't run it from Unity Editor?
Later when I export the game to launch it, I have persistent data I Json files that have to come with it for the game to work.
For clarity, here is the class that handles save/load of Json:
public class DataHandler
{
//Save Data
public static void saveData<T>(T dataToSave, string dataFileName)
{
string tempPath = Path.Combine(Application.persistentDataPath, "data");
tempPath = Path.Combine(tempPath, dataFileName + ".txt");
//Convert To Json then to bytes
string jsonData = JsonUtility.ToJson(dataToSave, true);
byte[] jsonByte = Encoding.ASCII.GetBytes(jsonData);
//Create Directory if it does not exist
if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
{
Directory.CreateDirectory(Path.GetDirectoryName(tempPath));
}
//Debug.Log(path);
try
{
File.WriteAllBytes(tempPath, jsonByte);
Debug.Log("Saved Data to: " + tempPath.Replace("/", "\\"));
}
catch (Exception e)
{
Debug.LogWarning("Failed To PlayerInfo Data to: " + tempPath.Replace("/", "\\"));
Debug.LogWarning("Error: " + e.Message);
}
}
//Load Data
public static T loadData<T>(string dataFileName)
{
string tempPath = Path.Combine(Application.persistentDataPath, "data");
tempPath = Path.Combine(tempPath, dataFileName + ".txt");
//Exit if Directory or File does not exist
if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
{
Debug.LogWarning("Directory does not exist");
return default(T);
}
if (!File.Exists(tempPath))
{
Debug.Log("File does not exist");
return default(T);
}
//Load saved Json
byte[] jsonByte = null;
try
{
jsonByte = File.ReadAllBytes(tempPath);
Debug.Log("Loaded Data from: " + tempPath.Replace("/", "\\"));
}
catch (Exception e)
{
Debug.LogWarning("Failed To Load Data from: " + tempPath.Replace("/", "\\"));
Debug.LogWarning("Error: " + e.Message);
}
//Convert to json string
string jsonData = Encoding.ASCII.GetString(jsonByte);
//Convert to Object
object resultValue = JsonUtility.FromJson<T>(jsonData);
return (T)Convert.ChangeType(resultValue, typeof(T));
}
public static bool deleteData(string dataFileName)
{
bool success = false;
//Load Data
string tempPath = Path.Combine(Application.persistentDataPath, "data");
tempPath = Path.Combine(tempPath, dataFileName + ".txt");
//Exit if Directory or File does not exist
if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
{
Debug.LogWarning("Directory does not exist");
return false;
}
if (!File.Exists(tempPath))
{
Debug.Log("File does not exist");
return false;
}
try
{
File.Delete(tempPath);
Debug.Log("Data deleted from: " + tempPath.Replace("/", "\\"));
success = true;
}
catch (Exception e)
{
Debug.LogWarning("Failed To Delete Data: " + e.Message);
}
return success;
}
}
In the answer below:
companyname = Company name from the Build Settings
productname = Product name from the Build Settings
Windows:
C:\Users\<userprofile>\AppData\LocalLow\<companyname>\<productname>
Windows Store:
%userprofile%\AppData\Local\Packages\<productname>\LocalState
Mac:
~/Library/Application Support/companyname/productname
older version of Unity on Mac:
~/Library/Caches folder
~/Library/Application Support/unity.companyname.productname.
Linux:
$XDG_CONFIG_HOME/unity3d/<companyname>/<productname>
which is the-same as
~/.config/unity3d/<companyname>/<productname>
Android:
/Data/Data/com.<companyname>.<productname>/files
with SD card on the Android device:
/storage/sdcard0/Android/data/com.<companyname>.<productname>/files
iOS:
/var/mobile/Containers/Data/Application/<RandomFolderName>/Documents
Example of the RandomFolderName full name:
/var/mobile/Containers/Data/Application/<055811B9-D125-41B1-A078-F898B06F8C58>/Documents
On iOS, you will be given access to the app's sandbox which is the Document folder. You must create folder inside this directory in order to create a new file inside it.
If you have a default data values in a json file, put the file in Assets/Resources folder so that it will be read-only then read it with TextAsset. When the game loads, you can use PlayerPrefs to check if this is the first time the game being loaded.
If this is the first time, use the value from TextAsset.text. If it is not use then value saved with the DataHandler class.
Roughly something like this:
if (PlayerPrefs.GetInt("FIRSTTIMEOPENING", 1) == 1)
{
Debug.Log("First Time Opening");
//Set first time opening to false
PlayerPrefs.SetInt("FIRSTTIMEOPENING", 0);
//USE TextAsset to load data
TextAsset txtAsset = (TextAsset)Resources.Load("player", typeof(TextAsset));
string tileFile = txtAsset.text;
PlayerInfo pInfo = JsonUtility.FromJson<PlayerInfo>(tileFile);
}
else
{
Debug.Log("NOT First Time Opening");
//USE DataHandler to load data
PlayerInfo pInfo = DataHandler.loadData<PlayerInfo>("player");
}