DEV Community

ibenge Uforo
ibenge Uforo

Posted on • Edited on

Building a telegram client with react-native (Part 1)

Building a telegram client was quite cumbersome but entertaining, stretching my knowledge and learning process which is key to growth

The notion of react-native learn once write anywhere rings true when faced with minimal apps but not with complex apps.

Although react-native has it’s good parts, being comfortable with the aforementioned languages would go along way even speeding up the process.

A couple of things learnt

  • Swift, Android(Java or kotlin) and bits of obj-c to comfortable write functionalities.
  • React-native being tedious when you have to combine languages, and would recommend one to go for the native directly except of course in minimal cases.
  • Threading and concurrency as most examples which improves the performance of the app, although a couple of tdlib methods can only be run on one thread.

Setup

If you are making use of an expo managed app, the process still applies.

  • Tdlib pre-built library. Android or build from scratch, see TDLib Android example
  • Create a folder called jniLibs under main
  • Move contents of the libs folder gotten from the built files above to jniLibs eg. armv4 etc
  • Move contents of tdlib→ *.java to your folder eg. nativeclient, client.java ... See Folder structure below, peek the jniLibs folder.

Folder structure

        sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/jniLibs']
            jni.srcDirs = []
        }
    }
Enter fullscreen mode Exit fullscreen mode
  • Connect to react-native using your preferred method. either turbo-modules or legacy.
  • Code!

Begin.

Within this module, we would cover while building upon the Java example within the TDLIB Repository.

  • Authentication with Telegram.
  • Display chat-list
  • Display single chat
  • Send and receive messages.

Authentication with Telegram.

To authenticate with telegram, you should have your phone number and details registered with telegram either through the app or built as an interface.

A couple of functions we would need.

  1. StartTdlib Service
  2. Login
  3. Verify Phone Number
  4. Resend OTP (exercise)

StartTdlib

This begins all communication to the TDLIB API

Variables.

/** client instance which would be used to make calls to the TDLIB api. **/
private static Client client = null;

/** 
Used to ensure we do not start the tdlib service once more, as it would throw an error
we can check for client above as well, if we have the client, this means  the service has been started
 **/
private static Boolean hasStartedTdlibService = false;
Enter fullscreen mode Exit fullscreen mode

Method

@ReactMethod
    public static void startTdLib() throws InterruptedException {

/** 
 ** We check if the tdlib service has been started and then trigger the last status
**/
        if (hasStartedTdlibService) {
            onAuthorizationStateUpdated(null);
            return;
        }

        // disable TDLib log and redirect fatal errors and plain log messages to a file
        Client.execute(new TdApi.SetLogVerbosityLevel(0));

                /* create a path wherein tdlib can send write logs to and throws an error with the message below if the directory cannot be accessed*/
        if (Client.execute(new TdApi.SetLogStream(new TdApi.LogStreamFile(reactContext.getExternalFilesDir(null).getAbsolutePath() + "tdlib.log", 1 << 27, false))) instanceof TdApi.Error) {
            throw new IOError(new IOException("Write access to the current directory is required"));
        }

        // create client
        client = Client.create(new UpdateHandler(), null, null);
        hasStartedTdlibService = true;

    }
Enter fullscreen mode Exit fullscreen mode

Functions not displayed from above are extracts from the repository example ie.

Login

On start of the TDLIB service, we would need to submit the phone number to receive the OTP code.

Native (Java)

@ReactMethod
    public void login(ReadableMap userDetails, Promise promise) {
        String phoneNumber = userDetails.getString("countrycode") + userDetails.getString("phoneNumber");
                // client here links to the variable above which has been filled by the startTdlib Method,.
        client.send(new TdApi.SetAuthenticationPhoneNumber(phoneNumber, null), new NativeResponse(promise));

   }
Enter fullscreen mode Exit fullscreen mode

React Native

Pending your UI requirements, ensure you have methods which

  • Calls the method ***Login* from above
  • Receives an event or promise for the next step to enter the OTP code.

Example submit function.

const handleSubmit =  () => {
        try {
                                // Calls the native method set above, ie. login sending the countrycode and phone number
                                TelegramModule.login({
                countrycode: methods.getValues('countryCode'),
                phoneNumber: methods.getValues('phoneNumber')
            });
        } catch (e: any) {
            ToastFeedback.showError(e.message);
        } finally {
                        // perform any clean up necessary.
        }
    };
Enter fullscreen mode Exit fullscreen mode

Example event listener

const eventEmitter = new NativeEventEmitter(TelegramModule);

eventEmitter.addListener('AuthorizationStateWaitCode', () => {
            // Perform necessary actions when this event is received from call in submit function
    });
Enter fullscreen mode Exit fullscreen mode

NB: Promise within the native module, is set from react native, no additional setup is required.

See for more info. Promises

Verify Phone Number

In this phase we would have received an OTP from the login process set above, with the OTP gotten we make setup some functions to handle the verification

Native (JAVA)

@ReactMethod
    public void verifyPhoneNumber(String otp, Promise promise) {
        client.send(new TdApi.CheckAuthenticationCode(otp), new NativeResponse(promise));
    }
Enter fullscreen mode Exit fullscreen mode

React Native

Likewise we ensure the core process listed above ie. submit function, eventlisteners are setup to proceed with the next step

const submitOtp = async () => {
        try {
            if (TelegramModule) {
                const result = await TelegramModule.verifyPhoneNumber(methods.getValues().code);
                if (result) {
                                // handle result here
                }
            }
        } catch (error: any) {
            // catch errors
        }
    };
Enter fullscreen mode Exit fullscreen mode

Exercise

see if you can implement the resend OTP phase, using the logic from above.

Some issues encountered along the way.

  • Sending responses from android to JS.
    • Event Emitter does not get called.
      • Check for incorrect handling of updates - in my case I attached the wrong response handler
    • Emitter does not send response back to react-native
      • Check you are making use of the correct event or method
    • API_INVALID__ERROR
      • check for spaces or strings within the setTdlibParameters
      • Make use of the second constructor with parameters.
  • Making use of the react application context in other classes
    • the idea was to instatiate the variable for the context with key of static when created and then access it through a static method.
  • Execution failed for task ':app:mergeDebugResources' occur while running the app
    • In my case it was an XML file having the wrong property ***“app:…”*** under a wrong layout. checking for resources eg. drawables, fonts, xml etc could resolve this issue.
  • Duplicate class androidx.lifecycle.ViewModelLazy found in modules jetified-lifecycle-viewmodel-ktx-2.3.1-runtime

    • As the error suggests there is a duplicate class used by one of the packages or your implementation with regards to the lifecycle - adding the following code unifies the dependency

    This is added on under app → build.gradle

    ```
    implementation "androidx.lifecycle:lifecycle-viewmodel:2.5.1"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
    ```
    
  • Textview or views not populated with data

    • each side of the display should contain the same ids as giving it a different name would not allow it to be populated.

Conclusion

In conclusion, building a Telegram clone with React-Native is a great way to explore the capabilities of this powerful framework. By following the steps outlined in this blog post, you should now have a basic understanding of how to set up the project native(java) and react-native.

Watch out for Part two as I would be taking a look at building the chat list and chat screens.

External Links

Top comments (0)