firebase-unity-sdk
firebase-unity-sdk copied to clipboard
[Question] GetFileAsync fails on iOS with unknown error
Please fill in the following fields:
- Unity editor version: 2022.1.8f1
- Firebase Unity SDK version: 8.10.1
- Source you installed the SDK: .tgz from https://developers.google.com/unity/archive#play_asset_delivery
- Problematic Firebase Component: storage
- Other Firebase Components in use: analytics, app, auth, crashlytics, dynamic-links, firestore, functions, remote-config
- Additional SDKs you are using: Facebook
- Platform you are using the Unity editor on: Windows
- Platform you are targeting: iOS
- Scripting Runtime: IL2CPP
Please describe the issue here:
We are trying to load file from Firebase storage using code:
var storageReference = storage.GetReferenceFromUrl(url)
storageReference .GetFileAsync()
We are using the same code on Windows, Android without any problems.
On iOS, however, the same logic fails with error:
An unknown error occurred, error code: 13000, httpResultCode: 300
The exception is not the informative in that case.
Any ideas why this is happening only on iOS, and what we can do to fix that issue?
Hi @aixaCode,
Thanks for reporting this issue. While I try to replicate this behavior, could you verify if the issue persists using the latest SDK version (9.1.0)?
Gonna respond for @aixaCode (we are in the same team), we can't update to 9.1.0 because Unity Cloud Build still uses older Xcode but we will as soon as the support from Unity is available. Hopefully this or next week.
Hi folks,
I haven't been able to replicate the issue so far. Could you provide a minimal, reproducible example of your implementation so that we can identify what's causing this? Could you also specify the iOS version of the device you're using?
Hi, we will try to get some repo example for you soon. All our iOS devices that we have been able to repro this issue were on iOS 15.5.
Hi, I have been able to prepare repro example. Here is the flow we use:
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using Firebase;
using Firebase.Auth;
using Firebase.Storage;
using UnityEngine;
public class FirebaseReproExample : MonoBehaviour
{
private const string STORAGE_URL = "gs://YOUR-NAME.com";
private const string PLATFORM = "iOS";
private const string FILE_NAME = "test.json";
private const string USER_LOGGED_KEY = "UserLogged";
private FirebaseApp app;
private FirebaseAuth auth;
private FirebaseUser user;
private FirebaseStorage storage;
private CancellationTokenSource cancellationToken;
private DependencyStatus dependencyStatus = DependencyStatus.UnavailableOther;
private void Start()
{
Startup(this.GetCancellationTokenOnDestroy()).Forget();
}
private async UniTaskVoid Startup(CancellationToken ct)
{
await InitializeFirebase(ct);
await InitializeStorage(ct);
await SignInSilently(ct);
await LoadCatalogFromFirebase(ct);
}
private async UniTask<bool> InitializeFirebase(CancellationToken ct)
{
bool success = false;
app = FirebaseApp.DefaultInstance;
Debug.Log($"Connected to: {app.Name}, {app.Options.ApiKey}, " +
$"{app.Options.AppId}, {app.Options.DatabaseUrl}, " +
$"{app.Options.ProjectId}");
var task = await FirebaseApp.CheckAndFixDependenciesAsync();
dependencyStatus = task;
if (dependencyStatus == DependencyStatus.Available)
{
Debug.Log("[FirebaseHandler] Firebase initialized");
success = true;
auth = FirebaseAuth.GetAuth(app);
auth.StateChanged += AuthStateChanged;
AuthStateChanged(this, null);
}
else
{
Debug.LogError($"[FirebaseHandler] Could not resolve all Firebase dependencies: {dependencyStatus}");
}
return success;
}
private void AuthStateChanged(object sender, EventArgs eventArgs)
{
if (auth.CurrentUser != user)
{
bool signedIn = auth.CurrentUser != null;
if (!signedIn && user != null)
{
Debug.Log($"[FirebaseHandler] Signed out {user.UserId}");
}
user = auth.CurrentUser;
if (signedIn)
{
Debug.Log($"[FirebaseHandler] Signed in {user.UserId}");
}
}
}
private UniTask InitializeStorage(CancellationToken ct)
{
storage = FirebaseStorage.GetInstance(app);
return default;
}
private async UniTask SignInSilently(CancellationToken ct)
{
var emailUserId = "[email protected]";
var password = emailUserId.Split('@')[0];
if (auth.CurrentUser == null)
{
Task<FirebaseUser> signInTask;
if (PlayerPrefs.HasKey(USER_LOGGED_KEY))
{
signInTask = auth.SignInWithEmailAndPasswordAsync(emailUserId, password);
}
else
{
signInTask = auth.CreateUserWithEmailAndPasswordAsync(emailUserId, password);
}
await signInTask.ContinueWith(task =>
{
if (task.IsCanceled)
{
Debug.Log($"[FirebaseHandler] SignInSilently canceled");
return;
}
if (task.IsFaulted)
{
Debug.LogError($"[FirebaseHandler] SignInSilently completed with error");
return;
}
user = task.Result;
PlayerPrefs.SetString(USER_LOGGED_KEY, user.Email);
Debug.Log($"[FirebaseHandler] SignInSilently completed with success - userId {user.UserId}");
}, ct);
}
}
private async UniTask<bool> LoadCatalogFromFirebase(CancellationToken cancellationToken)
{
var url = $"{STORAGE_URL}/{PLATFORM}/{FILE_NAME}";
Debug.Log($"Trying to load catalog from Firebase - {url}");
var storageReference = storage.GetReferenceFromUrl(url);
var success = true;
Debug.Log($"Storage reference - {storageReference.Name} {storageReference.Path} {storageReference.Bucket}");
var localPath = Path.Combine(Application.persistentDataPath, FILE_NAME);
var downloadTask = storageReference.GetFileAsync(localPath, cancelToken: cancellationToken);
await downloadTask.ContinueWith(task =>
{
if (task.IsCanceled)
{
success = false;
return;
}
if (task.IsFaulted)
{
success = false;
foreach (Exception exception in task.Exception.Flatten().InnerExceptions)
{
if (exception is StorageException firebaseEx)
{
Debug.LogError($"loading failed - {firebaseEx.ErrorCode} \n" +
$"{firebaseEx.Message}\n" +
$"{firebaseEx.InnerException}\n" +
$"{firebaseEx.HttpResultCode}\n" +
$"{firebaseEx.StackTrace}");
}
}
}
}, cancellationToken);
return success;
}
}
The logic is executed on Start
, so you can just attach this script to any GameObject and put it on the scene.
Additional info that might be useful:
- Storage rules:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write : if request.auth != null;
}
}
}
- Files structure in the storage is
gs://YOUR-APP-NAME/iOS/test.json
Let me know if you need any more info. And thanks for help :)
Thanks for the additional information, @aixaCode. I'm facing issues on my end given the provided information so far. Are there additional steps you performed along with adding this script? I'm seeing the usage of Cysharp in your code.
Ah right, I'm using UniTask package from https://github.com/Cysharp/UniTask.
You need to add to manifest.json
"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#2.3.1"
Thanks for the additional input, @aixaCode. I was able to reproduce the issue, and it seems odd that this is encountered on just the iOS platform. I'll let the team know about this, and you may refer to this thread for any updates.
@paulinon Hi, any update on the issue? We have updated the project to firebase 9.3.0, and the issue still persists.
@paulinon I had the same issue It works ok on Android but has the unknown error on iOS. I have used firebase 9.4.1, but the issue still persists.
@paulinon could we get some information about this? If this is going to be fixed. We are unable to use Firebase Storage with Unity Addressables and it's vital element of the product. Thanks in advance!
bumb, it's 3 months. Can we get update?
@paulinon We updated to 10.0.0, the same error
The issue still persists, but we just stopped using GetFileAsync
, and instead, we use GetDownloadUrlAsync
to fetch url, and then manually download the file to Application.persistentDataPath
via UnityWebRequest
Good moorning. We faced quite same issue, but with function GetBytesAsync
:
we use Firebase.Storage
to store some raw binary files. By a twist of fate I have the same account for login with test email through UnityEditor (win/mac) and through Apple Login on my IPhone.
We use PutBytesAsync
to upload data on storage. And it works on every (Editor win/mac, win Builds (il2cpp), Android builds (il2cpp), and iOS also) platform on the same way (as a ressult, we handle the same file on Storage's bucket: I Load it by hand from web-page and view from byte-viewer programm).
But when we call GetBytesAsync
we receive byte-array of the same length, but iOS download it with another content.
Unfortunatly, I'll be able to provide enhanced information this evening (about 8PM, UTC +3). But I believe that GetFileAsync
failed on the same reason: in some way iOS load byte-data in an inappropriate way.
At this evening I''ll provide both byte-array data that has been uploaded and that has been downloaded.
P.S.: we use very old version of SDK (8.2), but as I can see update version wasn't helpful for other colleagues. I'll update it to the latest version this night and report you about results next morning (UTC+3) this time.
But you may have some thoughts about it before my sdk-upgrade event, I'll glad to test them. Thanx a lot!
I've found the Firebase Storage SDK usage exmaple, but the only difference we have, we provide not 0
max content size:
private const long MAX_ALLOWED_SIZE = 1 * 1024 * 1024;
internal async Task<byte[]> LoadBytes(string key) {
return await UserStorageReference.Child(GetFilePath(key))
.GetBytesAsync(MAX_ALLOWED_SIZE)
.SafeExecuteOnMainThread<byte[], byte[], StorageException, int>(x => x, (ex, errorCode) => errorCode == FirebaseExtensions.OBJECT_NOT_FOUND_ERROR);
}
SafeExecuteOnMainThread
- it's extension suger to handle exceptions with provided specific errors and returns the result onMain-Thread
. It doesn't affect on functionGetBytesAsync
workflow.
For iOS i needed to add "file://" before the Application.PersistentDatapath for the save location.
https://stackoverflow.com/questions/1589930/so-what-is-the-right-direction-of-the-paths-slash-or-under-windows
@paulinon - same issue as above. adding "file://" before Application.PersistentDatapath did not work so far.
any other fixes?
Hey folks, I was able to reproduce the same behavior from the code snippet, however after tinkering around and putting this side by side with the quickstart, it looks like the main culprit of the issue could be due to the destination file path.
The minimal repro sample localPath:
Users/.../Library/Developer/CoreSimulator/Devices/.../data/Containers/Data/Application/.../Documents/File.txt
Firebase Unity quickstart storage:
file:///Users/.../Library/Developer/CoreSimulator/Devices/.../data/Containers/Data/Application/.../Documents/File.txt
The definitive difference from both is that the quickstart appends a "file://" for the destination path. I believe that this should help resolve the issue as intended.
@alexandramoisi you could also try checking out the implementation in our quickstart. It should be able to work correctly.
I'll go ahead and close this issue for now. Feel free to let me know if you'd like this to be reopened.
Thanks!