Hi,
When I try to switch the language during runtime I get NullReference Exceptions for each deactivated Text Label in the UI.
Is that expected behavior?
Edit: The error not only occurs on deactivated UI Texts but on all Texts that have been deactivated at least once in the past and might be active again.
NullReferenceException: Object reference not set to an instance of an object
UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationBase`1[TObject].remove_Completed (System.Action`1[T] value) (at Library/PackageCache/com.unity.addressables@1.3.8/Runtime/ResourceManager/AsyncOperations/AsyncOperationBase.cs:243)
UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle`1[TObject].remove_Completed (System.Action`1[T] value) (at Library/PackageCache/com.unity.addressables@1.3.8/Runtime/ResourceManager/AsyncOperations/AsyncOperationHandle.cs:112)
UnityEngine.Localization.LocalizedString.ClearLoadingOperation () (at Library/PackageCache/com.unity.localization@0.5.1-preview/Runtime/Localized Reference/LocalizedString.cs:171)
UnityEngine.Localization.LocalizedString.HandleLocaleChange (UnityEngine.Localization.Locale _) (at Library/PackageCache/com.unity.localization@0.5.1-preview/Runtime/Localized Reference/LocalizedString.cs:142)
UnityEngine.Localization.Settings.LocalizationSettings+<>c__DisplayClass55_0.<SendLocaleChangedEvents>b__0 (UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle`1[TObject] o) (at Library/PackageCache/com.unity.localization@0.5.1-preview/Runtime/Settings/LocalizationSettings.cs:270)
DelegateList`1[T].Invoke (T res) (at Library/PackageCache/com.unity.addressables@1.3.8/Runtime/ResourceManager/Util/DelegateList.cs:69)
UnityEngine.Debug:LogException(Exception)
DelegateList`1:Invoke(AsyncOperationHandle`1) (at Library/PackageCache/com.unity.addressables@1.3.8/Runtime/ResourceManager/Util/DelegateList.cs:73)
UnityEngine.ResourceManagement.ResourceManager:Update(Single)
MonoBehaviourCallbackHooks:Update() (at Library/PackageCache/com.unity.addressables@1.3.8/Runtime/ResourceManager/Util/MonoBehaviourCallbackHooks.cs:19)
No this is a bug. Thanks for reporting it.
I see the issue. Its caused when the data we load is immediately available and we dont use the Completed event. We will have a fix in the next release.
Hi,
We should have an update in the next 1-2 weeks.
For now, you can overwrite the contents of LocalizedString.cs file with this:
using System;
using UnityEngine.Localization.Settings;
using UnityEngine.Localization.SmartFormat;
using UnityEngine.Localization.Tables;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace UnityEngine.Localization
{
/// <summary>
/// A Localized String contains a reference to a <see cref="StringTableEntry"/> inside of a specific <see cref="StringTable"/>.
/// This provides a centralized way to work with localized strings.
/// </summary>
[Serializable]
public class LocalizedString : LocalizedReference
{
ChangeHandler m_ChangeHandler;
AsyncOperationHandle<LocalizedStringDatabase.TableEntryResult>? m_CurrentLoadingOperation;
LocalizedTable m_Table;
StringTableEntry m_Entry;
/// <summary>
/// Arguments that will be passed through to Smart Format. These arguments are not serialized and will need to be set during play mode.
/// </summary>
public SmartObjects Arguments { get; set; } = new SmartObjects();
/// <summary>
/// <inheritdoc cref="RegisterChangeHandler"/>
/// </summary>
/// <param name="value"></param>
public delegate void ChangeHandler(string value);
/// <summary>
/// The current loading operation for the string. A string may not be immediately available,
/// such as when loading the <see cref="StringTable"/>, so all string operations are wrapped
/// with an <see cref="AsyncOperationHandle"/>.
/// See also <seealso cref="RefreshString"/>
/// </summary>
public AsyncOperationHandle<LocalizedStringDatabase.TableEntryResult>? CurrentLoadingOperation
{
get => m_CurrentLoadingOperation;
internal set => m_CurrentLoadingOperation = value;
}
/// <summary>
/// Register a handler that will be called whenever a localized string is available.
/// When a handler is registered, the string will then be automatically loaded whenever the
/// <see cref="LocalizationSettings.SelectedLocaleChanged"/> is changed, during initialization and if
/// <see cref="RefreshString"/> is called.
/// <seealso cref="LoadAssetAsync"/> when not using a change handler.
/// </summary>
/// <param name="handler"></param>
public void RegisterChangeHandler(ChangeHandler handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
ClearChangeHandler();
m_ChangeHandler = handler ?? throw new ArgumentNullException(nameof(handler), "Handler must not be null");
LocalizationSettings.SelectedLocaleChanged += HandleLocaleChange;
ForceUpdate();
}
/// <summary>
/// Removes the handler and stops listening to changes to <see cref="LocalizationSettings.SelectedLocaleChanged"/>.
/// </summary>
public void ClearChangeHandler()
{
LocalizationSettings.ValidateSettingsExist();
LocalizationSettings.SelectedLocaleChanged -= HandleLocaleChange;
m_ChangeHandler = null;
ClearLoadingOperation();
}
/// <summary>
/// Forces a refresh of the string when using a <see cref="ChangeHandler"/>.
/// Note, this will only only force the refresh if there is currently no loading operation, if one is still being executed then it will be ignored and false will be returned.
/// If a string is not static and will change during game play, such as when using format arguments, then this can be used to force the string update itself.
/// </summary>
/// <returns>True if a refresh was requested or false if it could not.</returns>
public bool RefreshString()
{
if (m_ChangeHandler == null)
throw new Exception($"{nameof(RefreshString)} should be used with {nameof(RegisterChangeHandler)} however no change handler has been registered.");
if (m_CurrentLoadingOperation == null || !m_CurrentLoadingOperation.Value.IsDone)
return false;
string translatedText;
if (m_Entry != null)
{
translatedText = Arguments.Count == 0 ? m_Entry.GetLocalizedString() : m_Entry.GetLocalizedString(Arguments);
}
else
{
translatedText = LocalizationSettings.StringDatabase?.ProcessUntranslatedText(TableEntryReference.ResolveKeyName(m_Table?.Keys));
}
m_ChangeHandler(translatedText);
return true;
}
/// <summary>
/// This function will load the requested string table and return the translated string.
/// The Completed event will provide notification once the operation has finished and the string has been
/// found or an error has occurred, this will be called during LateUpdate.
/// It is possible that a string table may have already been loaded, such as during a previous operation
/// or when using Preload mode, the IsDone property can be checked as it is possible the translated
/// string is immediately available.
/// </summary>
/// <returns></returns>
public AsyncOperationHandle<string> GetLocalizedString()
{
LocalizationSettings.ValidateSettingsExist();
return LocalizationSettings.StringDatabase.GetLocalizedStringAsync(TableReference, TableEntryReference);
}
/// <summary>
/// Loads the requested string table and return the translated string after being formatted using the provided arguments.
/// The Completed event will provide notification once the operation has finished and the string has been
/// found or an error has occurred, this will be called during LateUpdate.
/// It is possible that a string table may have already been loaded, such as during a previous operation
/// or when using Preload mode, the IsDone property can be checked as it is possible the translated
/// string is immediately available.
/// </summary>
/// <param name="arguments">Arguments that will be passed to Smart Format or String.Format.</param>
/// <returns></returns>
public AsyncOperationHandle<string> GetLocalizedString(params object[] arguments)
{
LocalizationSettings.ValidateSettingsExist();
return LocalizationSettings.StringDatabase.GetLocalizedStringAsync(TableReference, TableEntryReference, arguments);
}
void ForceUpdate()
{
HandleLocaleChange(null);
}
void HandleLocaleChange(Locale _)
{
// Cancel any previous loading operations.
ClearLoadingOperation();
m_Entry = null;
m_Table = null;
CurrentLoadingOperation = LocalizationSettings.StringDatabase.GetTableEntryAsync(TableReference, TableEntryReference);
if (CurrentLoadingOperation.Value.IsDone)
AutomaticLoadingCompleted(CurrentLoadingOperation.Value);
else
CurrentLoadingOperation.Value.Completed += AutomaticLoadingCompleted;
}
void AutomaticLoadingCompleted(AsyncOperationHandle<LocalizedStringDatabase.TableEntryResult> loadOperation)
{
if (loadOperation.Status != AsyncOperationStatus.Succeeded)
{
CurrentLoadingOperation = null;
return;
}
m_Entry = loadOperation.Result.Entry;
m_Table = loadOperation.Result.Table;
RefreshString();
}
void ClearLoadingOperation()
{
if (CurrentLoadingOperation.HasValue)
{
// We should only call this if we are not done as its possible that the internal list is null if its not been used.
if (!CurrentLoadingOperation.Value.IsDone)
CurrentLoadingOperation.Value.Completed -= AutomaticLoadingCompleted;
CurrentLoadingOperation = null;
}
}
}
}