NullReferenceException on deactivated Text Objects when changing language

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)
1 Like

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.

Can’t wait! :slight_smile:

1 Like

Hi, I also encountered this problem and now I cannot switch languages. Is there an easy way to fix it?

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;
           }
       }
   }
}
3 Likes