Search Unity

Bug Firebase & FB SDKs Functionalities not working on the device (Only when built, in editor works fine)

Discussion in 'Scripting' started by Nicokam, Dec 18, 2020.

  1. Nicokam

    Nicokam

    Joined:
    May 28, 2020
    Posts:
    64
    I added the Firebase and Facebook SDK's in my unity project and something weird happened.

    My code works perfectly in the Unity Editor (Email/Password and Facebook Login) with no errors whatsoever. BUT, when I build it into iOS and try it out on my code, nothing regarding the SDK works, not even the button that makes the login (It should activate a message that says that I have to fill out the email field, etc..). Even if I fill out the information it doesn't do anything.
    The facebook button does trigger the login process, but when it comes to the end and starts to return from the facebook app to mine, it just returns to the login screen as if nothing had happened (It should go to the main screen instead).

    As I said in the beginning, everything IS WORKING PERFECTLY in the editor, so I'm guessing It's something not very complex but specific that I don't have to knowledge solve on my own, given that I have dedicated all day on this matter.

    Would really appreciate some help and please don't hesitate to ask for information that will help to accelerate this process.

    Here are the other posts I made (don't know if I modified something I shouldn't:
    #898 (comment)
    #892

    Here are some of the errors from the Xcode:
    CrashReporter: initialized 2020-12-17 19:49:32.185064-0300 zzzz[23059:8122575] Built from '2020.1/staging' branch, Version '2020.1.0f1 (2ab9c4179772)', Build type 'Release', Scripting Backend 'il2cpp' -> applicationDidFinishLaunching() 2020-12-17 19:49:32.257870-0300 zzzz[23059:8122575] Metal GPU Frame Capture Enabled 2020-12-17 19:49:32.258456-0300 zzzz[23059:8122575] Metal API Validation Enabled -> applicationDidBecomeActive() [Subsystems] Discovering subsystems at path /private/var/containers/Bundle/Application/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY/Data/UnitySubsystems GfxDevice: creating device client; threaded=1 Initializing Metal device caps: Apple A13 GPU Initialize engine version: 2020.1.0f1 (2ab9c4179772) CrashReporter: Could not parse crash report: Could not decode truncated crash log 2020-12-17 19:49:33.811029-0300 moovyn[23059:8122924] [Client] Updating selectors after delegate removal failed with: Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service on pid 92 named com.apple.commcenter.coretelephony.xpc was invalidated from this process." UserInfo={NSDebugDescription=The connection to service on pid 92 named com.apple.commcenter.coretelephony.xpc was invalidated from this process.} 2020-12-17 19:49:33.814584-0300 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX[23059:8122575] Unbalanced calls to begin/end appearance transitions for <UnityViewControllerStoryboard: 0x103487ac0>. 2020-12-17 19:49:33.823953-0300 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX[23059:8122919] [tcp] tcp_input [C2.1:3] flags=[R] seq=265141129, ack=0, win=0 state=CLOSED rcv_nxt=265141129, snd_una=1607627885 2020-12-17 19:49:33.824001-0300 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX[23059:8122919] [tcp] tcp_input [C2.1:3] flags=[R] seq=265141129, ack=0, win=0 state=CLOSED rcv_nxt=265141129, snd_una=1607627885 2020-12-17 19:49:33.849809-0300 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX[23059:8122923] FBSDKLog: fb-messenger-share-api is missing from your Info.plist under LSApplicationQueriesSchemes and is required. 2020-12-17 19:49:33.858905-0300 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX[23059:8122924] [tcp] tcp_input [C3.1:3] flags=[R] seq=2668679344, ack=0, win=0 state=CLOSED rcv_nxt=2668679344, snd_una=1421442049 2020-12-17 19:49:33.858980-0300 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX[23059:8122924] [tcp] tcp_input [C3.1:3] flags=[R] seq=2668679344, ack=0, win=0 state=CLOSED rcv_nxt=2668679344, snd_una=1421442049 2020-12-17 19:49:34.978091-0300 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX[23059:8122919] [tcp] tcp_input [C4.2:3] flags=[R] seq=4236490899, ack=0, win=0 state=CLOSED rcv_nxt=4236490899, snd_una=2789091866 2020-12-17 19:49:34.978937-0300 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX[23059:8122919] [tcp] tcp_input [C4.2:3] flags=[R] seq=4236490899, ack=0, win=0 state=CLOSED rcv_nxt=4236490899, snd_una=2789091866 UnloadTime: 4.260792 ms Setting up Firebase Auth UnityEngine.DebugLogHandler:Internal_Log(LogType, LogOption, String, Object) UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[]) UnityEngine.Logger:Log(LogType, Object) UnityEngine.Debug:Log(Object) AuthManager:InitializeFirebase() AuthManager:<Awake>b__12_0(Task1)
    System.Action1:Invoke(T) System.Threading.Tasks.ContinuationTaskFromResultTask1:InnerInvoke()
    System.Threading.Tasks.Task:Execute()
    System.Threading.Tasks.Task:ExecutionContextCallback(Object)
    System.Threading.ContextCallback:Invoke(Object)
    System.Threading.ExecutionContext:RunInternal(ExecutionContext, ContextCallback, Object, Boolean)
    System.Threading.ExecutionContext:Run(ExecutionContext, ContextCallback, Object, Boolean)
    System.Threading.Tasks.Task:ExecuteWithThreadLocal(Task&)
    System.Threading.Tasks.Task:ExecuteEntry(Boolean)
    System.Threading.Tasks.Task:System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
    System.Threading.ThreadPoolWorkQueue:Dispatch()
    System.Threading._ThreadPoolWaitCallback:performWaitCallback()

    (Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)

    Using Facebook Unity SDK v8.1.1 with FBiOSSDK/8.2.0
    UnityEngine.DebugLogHandler:Internal_Log(LogType, LogOption, String, Object)
    UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
    UnityEngine.Logger:Log(LogType, Object)
    UnityEngine.Debug:Log(Object)
    Facebook.Unity.DebugLogger:Info(String)
    Facebook.Unity.FacebookLogger:Info(String)
    Facebook.Unity.FB:LogVersion()
    Facebook.Unity.CompiledFacebookLoader:Start()

    And here, from the unity console once it is built in the iphone:
    [NORMAL LOGIN BUTTON]
    Autoconnected Player NullReferenceException: Object reference not set to an instance of an object.
    at AuthManager.Login (System.String _email, System.String _password) [0x00000] in <00000000000000000000000000000000>:0
    at AuthManager.LoginButton () [0x00000] in <00000000000000000000000000000000>:0
    at UnityEngine.Events.UnityAction.Invoke () [0x00000] in <00000000000000000000000000000000>:0
    at UnityEngine.Events.InvokableCall.Invoke () [0x00000] in <00000000000000000000000000000000>:0
    at UnityEngine.Events.UnityEvent.Invoke () [0x00000] in <00000000000000000000000000000000>:0
    at Lean.Gui.LeanButton.DoClick () [0x00000] in <00000000000000000000000000000000>:0
    at Lean.Gui.LeanButton.OnPointerUp (UnityEngine.EventSystems.PointerEventData eventData) [0x00000] in <00000000000000000000000000000000>:0
    at UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerUpHandler handler, UnityEngine.EventSystems.BaseEventData eventData) [0x00000] in <00000000000000000000000000000000>:0
    at UnityEngine.EventSystems.ExecuteEvents+EventFunction1[T1].Invoke (T1 handler, UnityEngine.EventSystems.BaseEventData eventData) [0x00000] in <00000000000000000000000000000000>:0 at UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction1[T1] functor) [0x00000] in <00000000000000000000000000000000>:0
    at UnityEngine.EventSystems.StandaloneInputModule.ProcessTouchPress (UnityEngine.EventSystems.PointerEventData pointerEvent, System.Boolean pressed, System.Boolean released) [0x00000] in <00000000000000000000000000000000>:0
    at UnityEngine.EventSystems.StandaloneInputModule.ProcessTouchEvents () [0x00000] in <00000000000000000000000000000000>:0
    at UnityEngine.EventSystems.StandaloneInputModule.Process () [0x00000] in <00000000000000000000000000000000>:0
    at UnityEngine.EventSystems.EventSystem.Update () [0x00000] in <00000000000000000000000000000000>:0
    UnityEngine.DebugLogHandler:Internal_LogException(Exception, Object)
    UnityEngine.DebugLogHandler:LogException(Exception, Object)
    UnityEngine.Logger:LogException(Exception, Object)
    UnityEngine.Debug:LogException(Exception)
    UnityEngine.EventSystems.ExecuteEvents:Execute(GameObject, BaseEventData, EventFunction`1)
    UnityEngine.EventSystems.StandaloneInputModule:processTouchPress(PointerEventData, Boolean, Boolean)
    UnityEngine.EventSystems.StandaloneInputModule:processTouchEvents()
    UnityEngine.EventSystems.StandaloneInputModule:process()
    UnityEngine.EventSystems.EventSystem:Update()

    (Filename: currently not available on il2cpp Line: -1)

    [FACEBOOK LOGIN BUTTON]
    Autoconnected Player NullReferenceException: Object reference not set to an instance of an object.
    at AuthManager.loginviaFirebaseFacebook (Facebook.Unity.AccessToken accessToken) [0x00000] in <00000000000000000000000000000000>:0
    at FacebookInit.authStatusCallback (Facebook.Unity.ILoginResult result) [0x00000] in <00000000000000000000000000000000>:0
    at Facebook.Unity.FacebookDelegate`1[T].Invoke (T result) [0x00000] in <00000000000000000000000000000000>:0
    at Facebook.Unity.CallbackManager.TryCallCallback[T] (System.Object callback, Facebook.Unity.IResult result) [0x00000] in <00000000000000000000000000000000>:0
    at Facebook.Unity.CallbackManager.CallCallback (System.Object callback, Facebook.Unity.IResult result) [0x00000] in <00000000000000000000000000000000>:0
    at Facebook.Unity.CallbackManager.OnFacebookResponse (Facebook.Unity.IInternalResult result) [0x00000] in <00000000000000000000000000000000>:0
    at Facebook.Unity.FacebookBase.OnAuthResponse (Facebook.Unity.LoginResult result) [0x00000] in <00000000000000000000000000000000>:0
    at Facebook.Unity.Mobile.MobileFacebook.OnLoginComplete (Facebook.Unity.ResultContainer resultContainer) [0x00000] in <00000000000000000000000000000000>:0
    at Facebook.Unity.FacebookGameObject.OnLoginComplete (System.String message) [0x00000] in <00000000000000000000000000000000>:0

    (Filename: currently not available on il2cpp Line: -1)

    I Decided to add the codes I am using too. Both of them are added to the same GameObject in unity. Here they are:

    AuthManager.cs
    Code (CSharp):
    1. `using System.Collections;
    2. using UnityEngine;
    3. using Firebase;
    4. using Firebase.Auth;
    5. using TMPro;
    6. using Facebook.Unity;
    7.  
    8. public class AuthManager : MonoBehaviour{
    9. //Firebase variables
    10. [Header("Firebase")]
    11. public DependencyStatus dependencyStatus;
    12. public FirebaseAuth auth;
    13. public FirebaseUser User;
    14.  
    15. //Login variables
    16. [Header("Login")]
    17. public TMP_InputField emailLoginField;
    18. public TMP_InputField passwordLoginField;
    19. public TMP_Text warningLoginText;
    20. public TMP_Text confirmLoginText;
    21.  
    22. //Register variables
    23. [Header("Register")]
    24. public TMP_InputField usernameRegisterField;
    25. public TMP_InputField emailRegisterField;
    26. public TMP_InputField passwordRegisterField;
    27. public TMP_InputField passwordRegisterVerifyField;
    28. public TMP_Text warningRegisterText;
    29.  
    30. void Awake() {
    31. //Check that all of the necessary dependencies for Firebase are present on the system
    32. FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>{
    33. dependencyStatus = task.Result;
    34. if (dependencyStatus == DependencyStatus.Available){
    35. //If they are avalible Initialize Firebase
    36. InitializeFirebase();
    37. }
    38. else {
    39. Debug.LogError("Could not resolve all Firebase dependencies: " + dependencyStatus);
    40. }
    41. });
    42. }
    43.  
    44. private void InitializeFirebase() {
    45. Debug.Log("Setting up Firebase Auth");
    46. //Set the authentication instance object
    47. auth = FirebaseAuth.DefaultInstance;
    48. auth.StateChanged += AuthStateChanged;
    49. AuthStateChanged(this, null);
    50. }
    51.  
    52. private void OnDestroy(){
    53. auth.StateChanged -= AuthStateChanged;
    54. auth = null;
    55. }
    56.  
    57. public void LoginButton() {
    58. Login(emailLoginField.text, passwordLoginField.text);
    59. }
    60.  
    61. public void RegisterButton(){
    62. StartCoroutine(Register(emailRegisterField.text, passwordRegisterField.text, usernameRegisterField.text));
    63. }
    64.  
    65. public void Login(string _email, string _password){
    66. var LoginTask = auth.SignInWithEmailAndPasswordAsync(_email, _password);
    67. LoginTask.ContinueWith(task => {
    68. if (task.IsCanceled || task.IsFaulted) {
    69. Firebase.FirebaseException e =
    70. task.Exception.Flatten().InnerExceptions[0] as Firebase.FirebaseException;
    71. GetErrorMessage((AuthError)e.ErrorCode, warningLoginText);
    72. return;
    73. }
    74.  
    75.    if (task.IsCompleted) {
    76.     User = LoginTask.Result;
    77.     Debug.LogFormat("User signed in successfully: {0} ({1})", User.DisplayName, User.Email);
    78.     warningLoginText.text = "";
    79.     confirmLoginText.text = "Logged In";
    80.     UIControllerLogin.instance.MainScreenScene();
    81.    }
    82. });
    83.  
    84. }
    85.  
    86. public IEnumerator Register(string _email, string _password, string _username) {
    87. if (_username == "") {
    88. //If the username field is blank show a warning
    89. warningRegisterText.text = "Missing Username";
    90. }
    91. else if (passwordRegisterField.text != passwordRegisterVerifyField.text) {
    92. //If the password does not match show a warning
    93. warningRegisterText.text = "Password Does Not Match!";
    94. }
    95. else {
    96. //Call the Firebase auth signin function passing the email and password
    97. var RegisterTask = auth.CreateUserWithEmailAndPasswordAsync(_email, _password);
    98. //Wait until the task completes
    99. yield return new WaitUntil(predicate: () => RegisterTask.IsCompleted);
    100.  
    101.   if (RegisterTask.Exception != null) {
    102.    //If there are errors handle them
    103.    FirebaseException e =
    104.        RegisterTask.Exception.Flatten().InnerExceptions[0] as FirebaseException;
    105.    GetErrorMessage((AuthError)e.ErrorCode, warningRegisterText);
    106.   }
    107.   else {
    108.    //User has now been created
    109.    //Now get the result
    110.    User = RegisterTask.Result;
    111.  
    112.    if (User != null) {
    113.      //Create a user profile and set the username
    114.      UserProfile profile = new UserProfile { DisplayName = _username };
    115.  
    116.      //Call the Firebase auth update user profile function passing the profile with the username
    117.      var ProfileTask = User.UpdateUserProfileAsync(profile);
    118.      //Wait until the task completes
    119.      yield return new WaitUntil(predicate: () => ProfileTask.IsCompleted);
    120.  
    121.      if (ProfileTask.Exception != null) {
    122.        //If there are errors handle them
    123.        Debug.LogWarning(message: $"Failed to register task with {ProfileTask.Exception}");
    124.        FirebaseException firebaseEx = ProfileTask.Exception.GetBaseException() as FirebaseException;
    125.        AuthError errorCode = (AuthError)firebaseEx.ErrorCode;
    126.        warningRegisterText.text = "Username Set Failed!";
    127.      }
    128.      else {
    129.        //Username is now set
    130.        //Now return to login screen
    131.        UIControllerLogin.instance.LoginScreenActive();
    132.        warningRegisterText.text = "";
    133.      }
    134.    }
    135.   }
    136. }
    137.  
    138. }
    139.  
    140. void AuthStateChanged(object sender, System.EventArgs eventArgs) {
    141. if (auth.CurrentUser != User) {
    142. bool signedIn = User != auth.CurrentUser && auth.CurrentUser != null;
    143. if (!signedIn && User != null) {
    144. Debug.Log("Signed out " + User.UserId);
    145. UIControllerLogin.instance.LoginScene();
    146. }
    147. User = auth.CurrentUser;
    148. if (signedIn) {
    149. Debug.Log("Signed in " + User.UserId);
    150. UIControllerLogin.instance.MainScreenScene();
    151. string displayName = User.DisplayName ?? "";
    152. string emailAddress = User.Email ?? "";
    153. // string photoUrl = User.PhotoUrl ?? "";
    154. string uid = User.UserId ?? "";
    155. }
    156. }
    157. }
    158.  
    159. void GetErrorMessage(AuthError errorCode, TMP_Text INFText) {
    160. string msg = "";
    161. msg = errorCode.ToString();
    162.  
    163. switch (errorCode) {
    164.   case AuthError.InvalidEmail:
    165.    INFText.text =
    166.      Lean.Localization.LeanLocalization.GetTranslationText($"Login/InvalidEmailOrPSW");
    167.    print(Lean.Localization.LeanLocalization.GetTranslationText($"Login/InvalidEmailOrPSW"));
    168.    break;
    169.  
    170.   case AuthError.WrongPassword:
    171.    INFText.text =
    172.      Lean.Localization.LeanLocalization.GetTranslationText($"Login/InvalidEmailOrPSW");
    173.    print(Lean.Localization.LeanLocalization.GetTranslationText($"Login/InvalidEmailOrPSW"));
    174.    break;
    175.  
    176.   case AuthError.AccountExistsWithDifferentCredentials:
    177.    INFText.text =
    178.      Lean.Localization.LeanLocalization.GetTranslationText($"Login/DiffCredentials");
    179.    print(Lean.Localization.LeanLocalization.GetTranslationText($"Login/DiffCredentials"));
    180.    break;
    181.  
    182.   case AuthError.MissingPassword:
    183.    INFText.text =
    184.      Lean.Localization.LeanLocalization.GetTranslationText($"Login/MissingPSW");
    185.    print(Lean.Localization.LeanLocalization.GetTranslationText($"Login/MissingPSW"));
    186.    break;
    187.  
    188.   case AuthError.MissingEmail:
    189.    INFText.text =
    190.      Lean.Localization.LeanLocalization.GetTranslationText($"Login/MissingEmail");
    191.    print(Lean.Localization.LeanLocalization.GetTranslationText($"Login/MissingEmail"));
    192.    break;
    193.  
    194.   case AuthError.EmailAlreadyInUse:
    195.    INFText.text =
    196.      Lean.Localization.LeanLocalization.GetTranslationText($"Login/EmailInUse");
    197.    print(Lean.Localization.LeanLocalization.GetTranslationText($"Login/EmailInUse"));
    198.    break;
    199.  
    200.   case AuthError.TooManyRequests:
    201.    INFText.text =
    202.      Lean.Localization.LeanLocalization.GetTranslationText($"Login/ManyRequests");
    203.    print(Lean.Localization.LeanLocalization.GetTranslationText($"Login/ManyRequests"));
    204.    break;
    205.  
    206.   case AuthError.SessionExpired:
    207.    INFText.text =
    208.              Lean.Localization.LeanLocalization.GetTranslationText($"Login/SessionExpired");
    209.    print(Lean.Localization.LeanLocalization.GetTranslationText($"Login/SessionExpired"));
    210.    break;
    211.  
    212.   case AuthError.WeakPassword:
    213.    INFText.text =
    214.      Lean.Localization.LeanLocalization.GetTranslationText($"Login/WeakPSW");
    215.    print(Lean.Localization.LeanLocalization.GetTranslationText($"Login/WeakPSW"));
    216.    break;
    217.  
    218.   case AuthError.UserNotFound:
    219.    INFText.text =
    220.      Lean.Localization.LeanLocalization.GetTranslationText($"Login/UserNotFound");
    221.    print(Lean.Localization.LeanLocalization.GetTranslationText($"Login/UserNotFound"));
    222.    break;
    223.  
    224.   default:
    225.    print($"errorCode: {msg}");
    226.    break;
    227. }
    228.  
    229. }
    230.  
    231. public void loginviaFirebaseFacebook(AccessToken accessToken) {
    232. Firebase.Auth.Credential credential =
    233. Firebase.Auth.FacebookAuthProvider.GetCredential(accessToken.TokenString);
    234. auth.SignInWithCredentialAsync(credential).ContinueWith(task => {
    235. if (task.IsCanceled) {
    236. Debug.LogError("SignInWithCredentialAsync was canceled.");
    237. warningLoginText.text = "SignInWithCredentialAsync was canceled";
    238. return;
    239. }
    240. if (task.IsFaulted) {
    241. Debug.LogError($"SignInWithCredentialAsync encountered an error: {task.Exception}");
    242. warningLoginText.text = $"SignInWithCredentialAsync encountered an error: {task.Exception}";
    243. return;
    244. }
    245.  
    246.   User = task.Result;
    247.   Debug.LogFormat("User signed in successfully: {0} ({1})", User.DisplayName, User.UserId);
    248.   UIControllerLogin.instance.MainScreenScene();
    249. });
    250.  
    251. }
    252.  
    253. }
    `

    FBManager.cs
    Code (CSharp):
    1. `using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Firebase.Auth;
    5. using Facebook.Unity;
    6.  
    7. public class FacebookInit : MonoBehaviour {
    8. private AuthManager AuthManagercs;
    9.  
    10. // Start function from Unity's MonoBehavior
    11. void Start() {
    12. AuthManagercs = GetComponent();
    13.  
    14. if (!FB.IsInitialized) {
    15.   FB.Init(initCallback, onHideUnity);
    16. }
    17. else {
    18.   // Already initialized
    19.   FB.ActivateApp();
    20. }
    21.  
    22. }
    23.  
    24. private void initCallback() {
    25. if (FB.IsInitialized) {
    26. // Signal an app activation App Event
    27. FB.ActivateApp();
    28. // Continue with Facebook SDK
    29. // ...
    30. }
    31. else {
    32. Debug.Log("Something went wrong to Initialize the Facebook SDK");
    33. }
    34. }
    35.  
    36. private void onHideUnity(bool isGameScreenShown) {
    37. if (!isGameScreenShown) {
    38. // Pause the game - we will need to hide
    39. Time.timeScale = 0;
    40. }
    41. else {
    42. // Resume the game - we're getting focus again
    43. Time.timeScale = 1;
    44. }
    45. }
    46.  
    47. public void FacebookButton() {
    48. // Permission option list [URL]https://developers.facebook.com/docs/facebook-login/permissions/[/URL]
    49. var permissons = new List()
    50. {"email",
    51. // "user_birthday",
    52. // "user_friends",
    53. "public_profile"};
    54. FB.LogInWithReadPermissions(permissons, authStatusCallback);
    55. }
    56.  
    57. private void authStatusCallback(ILoginResult result) {
    58. if (FB.IsLoggedIn) {
    59. // AccessToken class will have session details
    60. AccessToken accessToken = Facebook.Unity.AccessToken.CurrentAccessToken;
    61. // current access token's User ID : aToken.UserId
    62. AuthManagercs.loginviaFirebaseFacebook(accessToken);
    63. }
    64. else {
    65. Debug.Log("User cancelled login");
    66. }
    67. }
    68. }
    69. `