I am using Unity to develop an iOS application. I have been able to receive a stream from Revit while in the Unity Editor. In my app, I am not connected.
I have added the SDK, it is 2.19.0, I am using Unity 2022.3LTS. I believe that I read something about a SpeckleAccountManager prefab, but I can’t find any mention of it in the git repo.
I have also created a script to display the current account name. It works in the editor, but when I run it on my phone, the text reads “No Account Selected”.
What is the recommended practice here? Is my only option to implement my own authentication system? If so, what will be the best approach?
I am trying to register my application in the app.speckle.system and I can’t figure out what my redirect URL would be for a mobile application. Since a Unity application on a mobile device is not a web-server, how can I get this to work?
While we only officially support desktop platforms with our Unity connector, I may be able to provide some guidance.
I’ll be honest, I’ve never tested our Unity connector in iOS, but it sounds like you have the connector building and running, simply that account loading is not working.
We’ve stumbled across this before with building for android, and is expected for non-desktop planforms, but there may be some hoops you can jump through to get some form of send/receive working.
Please see this github ticket which explains the limitations we found when building for Android.
We had two problem:
SQLiteTransports don’t appear to be working
We need to specify auth tokens manulaly
The tl;dr is: you likely won’t beable to use the regular SpeckleReceiver and SpeckleSender components
In the past, I’ve found success using this very simple ManualReceive component. This allows you to manually specify an authtoken (see our docs on creating a personal access token).
To address your other comment, you will need to create an access tokens, not an application token.
I’d recommend taking the Manual receive component and testing this one out. Please let me know your findings.
I was able to get the ManualReceive script to receive model on my Mac in the editor. But I cannot get anything to show up on iOS yet.
You mentioned that SQLiteTransports don’t seem to be working, so I tried to force the location it saves the data to the Application.PersistentDataPath. As far as I know, thesis the only folder that is available to mobile devices to save and manage data onto the device.
When I try to change the file path, I get this error when I run it on my phone:
Receive process failed: Access to the path ‘/private/var/mobile/Containers/Data/Application/4FF484C4-1A92-48D3-8253-74EB3A83BAD7/.config/Speckle’ is denied.
Is there something within Core that is making the temporary file path use SQL?
Thanks
You could try setting this to a path that iOS allows read/write access.
However, the Manual receiver is designed to avoid using the SQLiteTransport entirely, instead using a MemoryTransport… But perhaps some other part of our SDK is to blame, and setting the path as mentioned is required.
We have the receive code working on Mac, but we hit an error when using the same code on iOS App on phones.
The stack trace shows that the logger is throwing an exception when we enter the ‘receive()’ method, we had hoped setting the blob ‘basepathoverride’ would tell the logger to use our custom path, but are now stuck on the logger exception.
If we can pass the logger exception (like we do in Unity on Mac OSX), then the rest of the receive logic works and creates our assets as expected – we’re just stuck on the iOS logger exception.
using System;
using System.Collections;
using System.IO;
using System.Threading.Tasks;
using Speckle.ConnectorUnity;
using Speckle.ConnectorUnity.Components;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using Speckle.Core.Helpers;
using Speckle.Core.Models;
using Speckle.Core.Transports;
using UnityEngine;
[AddComponentMenu("Speckle/Extras/Manual Receiver")]
[RequireComponent(typeof(RecursiveConverter))]
public class ManualReceive : MonoBehaviour
{
public string authToken;
public string serverUrl;
public string streamId;
public string objectId;
private RecursiveConverter receiver;
private string customSpecklePath;
void Awake()
{
receiver = GetComponent<RecursiveConverter>();
}
IEnumerator Start()
{
Debug.developerConsoleVisible = true;
if (Time.timeSinceLevelLoad > 20)
yield return null;
// Initialize custom path on the main thread
customSpecklePath = Path.Combine(Application.persistentDataPath, "Speckle");
if (!Directory.Exists(customSpecklePath))
{
Directory.CreateDirectory(customSpecklePath);
}
SpecklePathProvider.OverrideApplicationDataPath(customSpecklePath);
SpecklePathProvider.OverrideBlobStorageFolder(customSpecklePath);
Receive();
}
[ContextMenu(nameof(Receive))]
public void Receive()
{
var account = new Account()
{
token = authToken,
serverInfo = new ServerInfo() { url = serverUrl },
};
string path = customSpecklePath; // Capture the path in a local variable
Task.Run(async () =>
{
try
{
Debug.Log("Starting the Receive process.");
using ServerTransport transport = new(account, streamId, blobStorageFolder: customSpecklePath);
MemoryTransport localTransport = new();
Base speckleObject = await Operations.Receive(
objectId,
remoteTransport: transport,
localTransport: localTransport
);
if (speckleObject == null) throw new Exception("received data was null!");
Dispatcher.Instance().Enqueue(() =>
{
var parentObject = new GameObject(name);
receiver.RecursivelyConvertToNative_Sync(speckleObject, parentObject.transform);
Debug.Log($"Receive {objectId} completed");
});
}
catch (System.Exception ex)
{
Debug.LogError($"Receive process failed: {ex.Message}\n{ex.StackTrace}");
}
});
}
}
Here is our Stack Trace:
Receive process failed: /private/var/containers/Bundle/Application/C7C3C4AF-8D84-4123-8A71-56E389B26E17/ARLAxonometric.app/SpeckleCore2.dll
at System.Diagnostics.FileVersionInfo.GetVersionInfo (System.String fileName) [0x00000] in <00000000000000000000000000000000>:0
at Speckle.Core.Logging.SpeckleLog.CreateConfiguredLogger (System.String hostApplicationName, System.String hostApplicationVersion, Speckle.Core.Logging.SpeckleLogConfiguration logConfiguration) [0x00000] in <00000000000000000000000000000000>:0
at Speckle.Core.Logging.SpeckleLog.Initialize (System.String hostApplicationName, System.String hostApplicationVersion, Speckle.Core.Logging.SpeckleLogConfiguration logConfiguration) [0x00000] in <00000000000000000000000000000000>:0
at Speckle.Core.Logging.SpeckleLog.get_Logger () [0x00000] in <00000000000000000000000000000000>:0
at Speckle.Core.Transports.ServerTransport.Initialize (System.String baseUri) [0x00000] in <00000000000000000000000000000000>:0
at Speckle.Core.Transports.ServerTransport..ctor (Speckle.Core.Credentials.Account account, System.String streamId, System.Int32 timeoutSeconds, System.String blobStorageFolder) [0x00000] in <00000000000000000000000000000000>:0
at ManualReceive+<>c__DisplayClass8_0.<Receive>b__0 () [0x00000] in <00000000000000000000000000000000>:0
at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine] (TStateMachine& stateMachine) [0x00000] in <00000000000000000000000000000000>:0
at ManualReceive+<>c__DisplayClass8_0.<Receive>b__0 () [0x00000] in <00000000000000000000000000000000>:0
at System.Threading.Tasks.Task`1[TResult].InnerInvoke () [0x00000] in <00000000000000000000000000000000>:0
at System.Threading.Tasks.Task.Execute () [0x00000] in <00000000000000000000000000000000>:0
at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
at System.Threading.Tasks.Task.ExecuteWithThreadLocal (System.Threading.Tasks.Task& currentTaskSlot) [0x00000] in <00000000000000000000000000000000>:0
at System.Threading.Tasks.Task.ExecuteEntry (System.Boolean bPreventDoubleExecution) [0x00000] in <00000000000000000000000000000000>:0
at System.Threading.ThreadPoolWorkQueue.Dispatch () [0x00000] in <00000000000000000000000000000000>:0
<<Receive>b__0>d:MoveNext()
System.Runtime.CompilerServices.AsyncTaskMethodBuilder:Start(TStateMachine&)
<>c__DisplayClass8_0:<Receive>b__0()
System.Threading.Tasks.Task`1:InnerInvoke()
System.Threading.Tasks.Task:Execute()
System.Threading.ExecutionContext:RunInternal(ExecutionContext, ContextCallback, Object, Boolean)
System.Threading.Tasks.Task:ExecuteWithThreadLocal(Task&)
System.Threading.Tasks.Task:ExecuteEntry(Boolean)
System.Threading.ThreadPoolWorkQueue:Dispatch()
It appears to be failing in the call to FileVersionInfo.GetVersionInfo in this function.
I would recomend trying to find what path this Assembly.GetExecutingAssembly().Location is returning when running in ios.
Perhaps you can add this to a custom script and print the result to console just to test.
Hi Jedd, Thanks for following up. I have not been able to make progress on this, unfortunately.
I am applying my energy to a desktop version of my product so that I can make some actual progress right now. Ultimately, I will want to port my application onto a mobile device and use AR.
The manual receive would hopefully prove that models can be received with the next step being auth. But I am a one man band for now and I think mobile auth might be outside my capabilities. Any assistance here would be extremely appreciated.
Cheers