Hi Morten,
Sorry I should have followed up with you on this earlier. Your original question about whether or not the build server VS versions varied significantly got me thinking that this might indeed be the problem, since our versions did vary significantly. Unfortunately, I've now upgraded the servers, so I can't tell you exactly what they used to be, but it was 2017 on the build server (unknown exact version), and 2019 for development (15.2.x).
For the current ios build we're on 8.2.6 (2019), and for Android we're on on 16.2.5 (also 2019).
Upgrading these servers did actually address the ios crash, but not Android. And even for ios, while the crash did go away, we still were experiencing map initialization failures, which actually turned out to be caused by some fairly hacky stuff we were doing on startup. Effectively, in order to ascertain 'true' edit/delete/add-feature capabilities, we are basically doing a test add/edit/update and checking to see if the Runtime throws an exception, and then rolling back the edits. Rolling back edits it seems was causing the crash with WMTS, but there was also ocassionally the stack trace below, which seems a lot more related to what we were doing. I still have no idea why rolling back edits would cause the WMTS issue, but it seems to be gone now through a combination of upgrading the build server, and deferring our hack to test editing capabilities.
BACKGROUND THREAD 27 - CRASHED
ArcGIS-arm64
Esri_runtimecore::Geodatabase::Table::revert_changes(Esri_runtimecore::Geodatabase::Difference_from, boost::optional<Esri_runtimecore::Common::Date_time>)
ArcGIS-arm64
std::__1::__function::__func<auto Esri_runtimecore::Mapping::Service_feature_table::undo_local_edits(Esri_runtimecore::Mapping::Task_options const&)::$_36::operator()<Esri_runtimecore::Common::Weak_visitor_ptr<Esri_runtimecore::Mapping::Service_feature_table>, Esri_runtimecore::Mapping::Task_completion_source<void>, Esri_runtimecore::Mapping::Task_options>(Esri_runtimecore::Common::Weak_visitor_ptr<Esri_runtimecore::Mapping::Service_feature_table>&, Esri_runtimecore::Mapping::Task_completion_source<void>&, Esri_runtimecore::Mapping::Task_options&)::'lambda'(), std::__1::allocator<std::__1::allocator>, void ()>::operator()()
ArcGIS-arm64
std::__1::__function::__func<Esri_runtimecore::Mapping::Task<void>::Task(std::__1::function<void ()>, Esri_runtimecore::Mapping::Task_options)::$_1, std::__1::allocator<Esri_runtimecore::Mapping::Task<void>::Task(std::__1::function<void ()>, Esri_runtimecore::Mapping::Task_options)::$_1>, boost::any ()>::operator()()
ArcGIS-arm64
std::__1::__function::__func<Esri_runtimecore::Mapping::Task_implementation::Task_implementation(std::__1::function<boost::any ()>, Esri_runtimecore::Mapping::Task_options)::$_0, std::__1::allocator<Esri_runtimecore::Mapping::Task_implementation::Task_implementation(std::__1::function<boost::any ()>, Esri_runtimecore::Mapping::Task_options)::$_0>, boost::any ()>::operator()()
ArcGIS-arm64
pplx::details::_PPLTaskHandle<boost::any, pplx::task<boost::any>::_InitialTaskHandle<boost::any, Esri_runtimecore::Mapping::Task_implementation::Task_implementation(std::__1::function<boost::any ()>, Esri_runtimecore::Mapping::Task_options)::$_0, pplx::details::_TypeSelectorNoAsync>, pplx::details::_TaskProcHandle>::invoke() const
ArcGIS-arm64
pplx::details::_TaskProcHandle::_RunChoreBridge(void*)
ArcGIS-arm64
Esri_runtimecore::Common::Core_scheduler::bridge_proc_(void*)
ArcGIS-arm64
Esri_runtimecore::Common::Core_scheduler::bridge_proc_(void*)
libdispatch.dylib
_dispatch_client_callout
If you're wondering why we're doing this hack to check edit capabilities, it's because we don't have a good way from the ArcGIS Runtime to check whether or not the user has permissions to update/delete/add. The CanUpdate, CanAdd, CanDelete properties on a FeatureTable don't take licensing and anonymous access into account (see bug ENH-000124520), and LayerInfo OwnershipBasedAccessControl is not populated in all cases (case #02370084 with Esri support).
For full reference, here is our code to truly check if the user has for example update permission:
If there was a nice little method we could call in the Runtime, that would save us from having to do all this hacky work!
public override async Task<bool> UserCanUpdate()
{
var license = ArcGISRuntimeEnvironment.GetLicense();
if (license.LicenseLevel == LicenseLevel.Lite && Capabilities.SupportsUpdate)
{
// If we have a Lite-level license, the previous check is not enough to ensure that we are reporting correct values,
// we have to assert that anonymous adding and editing are also allowed for this layer (e.g. it must be a publicly shared
// layer with editing enabled)
if (_supportsAnonymousUpdate == null)
{
_supportsAnonymousUpdate = Layer.FeatureTable.CanUpdateAnonymously();
}
bool supportsAnonymousUpdate = await _supportsAnonymousUpdate;
return supportsAnonymousUpdate;
}
return Capabilities.SupportsUpdate;
}
internal static async Task<bool> CanUpdateAnonymously(this FeatureTable table)
{
bool canEdit = false;
if (table is ServiceFeatureTable sft &&
sft.LayerInfo.OwnershipBasedAccessControl != null &&
sft.LayerInfo.OwnershipBasedAccessControl.AllowOthersToUpdate)
{
// If the layer has Ownership-Based Access Control (OBAC) configured AND users other than the creator are able to edit the
// feature, we can skip manually testing the update operation and instead can rely on this property.
canEdit = sft.LayerInfo.OwnershipBasedAccessControl.AllowAnonymousToUpdate;
}
else
{
try
{
// If we are using a Lite level license and the layer does not support OBAC, the best we can do is attempt an "empty" update
// and catch the error if it fails. (the CanUpdate() method does not return accurate results in this case).
var testFeature = await table.QuerySingleFeatureAsync();
if (testFeature == null)
{
// If there are no features, we'll have to add a dummy one for testing (which we will remove after).
testFeature = table.CreateFeature();
await table.AddFeatureAsync(testFeature);
}
if (testFeature is ArcGISFeature arcgisFeature && arcgisFeature.LoadStatus != Esri.ArcGISRuntime.LoadStatus.Loaded)
{
// Need to load the feature before calling the update.
await arcgisFeature.LoadAsync();
}
// Test update.
await table.UpdateFeatureAsync(testFeature);
canEdit = true;
}
catch
{
// The update failed, so we know for sure that we both cannot update nor delete (since delete is never a
// standalone permission).
canEdit = false;
}
finally
{
// Ensure we clear out any edits we made.
try
{
await table.ClearLocalEditsAsync();
}
catch (Exception) { }
}
}
return canEdit;
}