Search Unity

NullReferenceException on deactivated Text Objects when changing language

Discussion in 'Localization Tools' started by SwampyTimmy, Dec 16, 2019.

  1. SwampyTimmy

    SwampyTimmy

    Joined:
    Apr 18, 2015
    Posts:
    4
    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.


    Code (CSharp):
    1. NullReferenceException: Object reference not set to an instance of an object
    2. 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)
    3. 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)
    4. UnityEngine.Localization.LocalizedString.ClearLoadingOperation () (at Library/PackageCache/com.unity.localization@0.5.1-preview/Runtime/Localized Reference/LocalizedString.cs:171)
    5. UnityEngine.Localization.LocalizedString.HandleLocaleChange (UnityEngine.Localization.Locale _) (at Library/PackageCache/com.unity.localization@0.5.1-preview/Runtime/Localized Reference/LocalizedString.cs:142)
    6. 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)
    7. DelegateList`1[T].Invoke (T res) (at Library/PackageCache/com.unity.addressables@1.3.8/Runtime/ResourceManager/Util/DelegateList.cs:69)
    8. UnityEngine.Debug:LogException(Exception)
    9. DelegateList`1:Invoke(AsyncOperationHandle`1) (at Library/PackageCache/com.unity.addressables@1.3.8/Runtime/ResourceManager/Util/DelegateList.cs:73)
    10. UnityEngine.ResourceManagement.ResourceManager:Update(Single)
    11. MonoBehaviourCallbackHooks:Update() (at Library/PackageCache/com.unity.addressables@1.3.8/Runtime/ResourceManager/Util/MonoBehaviourCallbackHooks.cs:19)
    12.  
     
    Last edited: Dec 16, 2019
    Lukas-Labaj likes this.
  2. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,281
    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.
     
    Last edited: Dec 16, 2019
  3. SwampyTimmy

    SwampyTimmy

    Joined:
    Apr 18, 2015
    Posts:
    4
    Can't wait! :)
     
    karl_jones likes this.
  4. AngelBeatsZzz

    AngelBeatsZzz

    Joined:
    Nov 24, 2017
    Posts:
    239
    Hi, I also encountered this problem and now I cannot switch languages. Is there an easy way to fix it?
     
  5. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,281
    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:
    Code (csharp):
    1. using System;
    2. using UnityEngine.Localization.Settings;
    3. using UnityEngine.Localization.SmartFormat;
    4. using UnityEngine.Localization.Tables;
    5. using UnityEngine.ResourceManagement.AsyncOperations;
    6.  
    7. namespace UnityEngine.Localization
    8. {
    9.    /// <summary>
    10.    /// A Localized String contains a reference to a <see cref="StringTableEntry"/> inside of a specific <see cref="StringTable"/>.
    11.    /// This provides a centralized way to work with localized strings.
    12.    /// </summary>
    13.    [Serializable]
    14.    public class LocalizedString : LocalizedReference
    15.    {
    16.        ChangeHandler m_ChangeHandler;
    17.  
    18.        AsyncOperationHandle<LocalizedStringDatabase.TableEntryResult>? m_CurrentLoadingOperation;
    19.        LocalizedTable m_Table;
    20.        StringTableEntry m_Entry;
    21.  
    22.        /// <summary>
    23.        /// Arguments that will be passed through to Smart Format. These arguments are not serialized and will need to be set during play mode.
    24.        /// </summary>
    25.        public SmartObjects Arguments { get; set; } = new SmartObjects();
    26.  
    27.        /// <summary>
    28.        /// <inheritdoc cref="RegisterChangeHandler"/>
    29.        /// </summary>
    30.        /// <param name="value"></param>
    31.        public delegate void ChangeHandler(string value);
    32.  
    33.        /// <summary>
    34.        /// The current loading operation for the string. A string may not be immediately available,
    35.        /// such as when loading the <see cref="StringTable"/>, so all string operations are wrapped
    36.        /// with an <see cref="AsyncOperationHandle"/>.
    37.        /// See also <seealso cref="RefreshString"/>
    38.        /// </summary>
    39.        public AsyncOperationHandle<LocalizedStringDatabase.TableEntryResult>? CurrentLoadingOperation
    40.        {
    41.            get => m_CurrentLoadingOperation;
    42.            internal set => m_CurrentLoadingOperation = value;
    43.        }
    44.  
    45.        /// <summary>
    46.        /// Register a handler that will be called whenever a localized string is available.
    47.        /// When a handler is registered, the string will then be automatically loaded whenever the
    48.        /// <see cref="LocalizationSettings.SelectedLocaleChanged"/> is changed, during initialization and if
    49.        /// <see cref="RefreshString"/> is called.
    50.        /// <seealso cref="LoadAssetAsync"/> when not using a change handler.
    51.        /// </summary>
    52.        /// <param name="handler"></param>
    53.        public void RegisterChangeHandler(ChangeHandler handler)
    54.        {
    55.            if (handler == null)
    56.                throw new ArgumentNullException(nameof(handler));
    57.  
    58.            ClearChangeHandler();
    59.            m_ChangeHandler = handler ?? throw new ArgumentNullException(nameof(handler), "Handler must not be null");
    60.            LocalizationSettings.SelectedLocaleChanged += HandleLocaleChange;
    61.  
    62.            ForceUpdate();
    63.        }
    64.  
    65.        /// <summary>
    66.        /// Removes the handler and stops listening to changes to <see cref="LocalizationSettings.SelectedLocaleChanged"/>.
    67.        /// </summary>
    68.        public void ClearChangeHandler()
    69.        {
    70.            LocalizationSettings.ValidateSettingsExist();
    71.            LocalizationSettings.SelectedLocaleChanged -= HandleLocaleChange;
    72.            m_ChangeHandler = null;
    73.            ClearLoadingOperation();
    74.        }
    75.      
    76.        /// <summary>
    77.        /// Forces a refresh of the string when using a <see cref="ChangeHandler"/>.
    78.        /// 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.
    79.        /// 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.
    80.        /// </summary>
    81.        /// <returns>True if a refresh was requested or false if it could not.</returns>
    82.        public bool RefreshString()
    83.        {
    84.            if (m_ChangeHandler == null)
    85.                throw new Exception($"{nameof(RefreshString)} should be used with {nameof(RegisterChangeHandler)} however no change handler has been registered.");
    86.  
    87.            if (m_CurrentLoadingOperation == null || !m_CurrentLoadingOperation.Value.IsDone)
    88.                return false;
    89.  
    90.            string translatedText;
    91.            if (m_Entry != null)
    92.            {
    93.                translatedText = Arguments.Count == 0 ? m_Entry.GetLocalizedString() : m_Entry.GetLocalizedString(Arguments);
    94.            }
    95.            else
    96.            {
    97.                translatedText = LocalizationSettings.StringDatabase?.ProcessUntranslatedText(TableEntryReference.ResolveKeyName(m_Table?.Keys));
    98.            }
    99.            m_ChangeHandler(translatedText);
    100.            return true;
    101.        }
    102.  
    103.        /// <summary>
    104.        /// This function will load the requested string table and return the translated string.
    105.        /// The Completed event will provide notification once the operation has finished and the string has been
    106.        /// found or an error has occurred, this will be called during LateUpdate.
    107.        /// It is possible that a string table may have already been loaded, such as during a previous operation
    108.        /// or when using Preload mode, the IsDone property can be checked as it is possible the translated
    109.        /// string is immediately available.
    110.        /// </summary>
    111.        /// <returns></returns>
    112.        public AsyncOperationHandle<string> GetLocalizedString()
    113.        {
    114.            LocalizationSettings.ValidateSettingsExist();
    115.            return LocalizationSettings.StringDatabase.GetLocalizedStringAsync(TableReference, TableEntryReference);
    116.        }
    117.  
    118.        /// <summary>
    119.        /// Loads the requested string table and return the translated string after being formatted using the provided arguments.
    120.        /// The Completed event will provide notification once the operation has finished and the string has been
    121.        /// found or an error has occurred, this will be called during LateUpdate.
    122.        /// It is possible that a string table may have already been loaded, such as during a previous operation
    123.        /// or when using Preload mode, the IsDone property can be checked as it is possible the translated
    124.        /// string is immediately available.
    125.        /// </summary>
    126.        /// <param name="arguments">Arguments that will be passed to Smart Format or String.Format.</param>
    127.        /// <returns></returns>
    128.        public AsyncOperationHandle<string> GetLocalizedString(params object[] arguments)
    129.        {
    130.            LocalizationSettings.ValidateSettingsExist();
    131.            return LocalizationSettings.StringDatabase.GetLocalizedStringAsync(TableReference, TableEntryReference, arguments);
    132.        }
    133.      
    134.        void ForceUpdate()
    135.        {
    136.            HandleLocaleChange(null);
    137.        }
    138.  
    139.        void HandleLocaleChange(Locale _)
    140.        {
    141.            // Cancel any previous loading operations.
    142.            ClearLoadingOperation();
    143.  
    144.            m_Entry = null;
    145.            m_Table = null;
    146.  
    147.            CurrentLoadingOperation = LocalizationSettings.StringDatabase.GetTableEntryAsync(TableReference, TableEntryReference);
    148.            if (CurrentLoadingOperation.Value.IsDone)
    149.                AutomaticLoadingCompleted(CurrentLoadingOperation.Value);
    150.            else
    151.                CurrentLoadingOperation.Value.Completed += AutomaticLoadingCompleted;
    152.            }
    153.  
    154.        void AutomaticLoadingCompleted(AsyncOperationHandle<LocalizedStringDatabase.TableEntryResult> loadOperation)
    155.        {
    156.            if (loadOperation.Status != AsyncOperationStatus.Succeeded)
    157.            {
    158.                CurrentLoadingOperation = null;
    159.                return;
    160.            }
    161.  
    162.            m_Entry = loadOperation.Result.Entry;
    163.            m_Table = loadOperation.Result.Table;
    164.            RefreshString();
    165.        }
    166.  
    167.        void ClearLoadingOperation()
    168.        {
    169.            if (CurrentLoadingOperation.HasValue)
    170.            {
    171.                // We should only call this if we are not done as its possible that the internal list is null if its not been used.
    172.                if (!CurrentLoadingOperation.Value.IsDone)
    173.                    CurrentLoadingOperation.Value.Completed -= AutomaticLoadingCompleted;
    174.                CurrentLoadingOperation = null;
    175.            }
    176.        }
    177.    }
    178. }
     
    Last edited: Jan 10, 2020