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.
- Add to build.gradle(:app) android - see, https://github.com/tdlib/td/issues/1904
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jniLibs']
jni.srcDirs = []
}
}
- 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.
- StartTdlib Service
- Login
- Verify Phone Number
- 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;
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;
}
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));
}
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.
}
};
Example event listener
const eventEmitter = new NativeEventEmitter(TelegramModule);
eventEmitter.addListener('AuthorizationStateWaitCode', () => {
// Perform necessary actions when this event is received from call in submit function
});
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));
}
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
}
};
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.
- Event Emitter does not get called.
- 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
- Twitter: @benny_wayn
- LinkedIn: Ibenge-uforo
- Ongoing Research[5 min]: Social Survey
Top comments (0)