In this article, I would like to share my experiences with mobile projects and discuss the challenges and differences between using React Native and Flutter. Mobile projects require specific skills and knowledge of mobile design patterns. In this article, I will focus on hybrid development, which involves using a single codebase for both Android and iOS platforms.
Choosing the Development Machine: I prefer coding on a MacBook due to its performance advantages. It allows me to use both the Android Studio emulator for simulating Android devices and the native Xcode emulator on macOS for simulating iPhones, iPads, and other Apple products.
Choosing the IDE: Despite recommendations from other mobile developers, I personally feel more comfortable using VSCode as my IDE. Coming from a web development background, I find it easier to work with due to familiarity. I can also install plugins that assist me during coding.
Testing on Physical Devices: To test the application while coding, I prefer using a physical device connected to my computer. For Android, I can compile the code using Wi-Fi by connecting both devices to the same network. For iOS, I initially require a cable for the first compilation but can subsequently use a wireless connection.
Flutter: With Flutter, I noticed that the components and libraries were more distributed compared to React Native. It required a different approach, but being natively compiled, it offered its advantages.
In mobile development, I typically divide the project structure into the following layers:
Domain Layer: Contains the business rules and logic.
Data Layer: Handles connections to the backend or serverless databases and offline data storage.
Presentation Layer: Includes the visual components and screens. I prefer a decoupled architecture that allows an extra layer for field validation between the presentation and data layers.
In mobile development, I highly recommend using a strongly typed structure. In React Native, I find the use of TypeScript beneficial, while Flutter has built-in support for it. Typed development provides better type control and allows for the application of design patterns more seamlessly, especially for developers with a background in languages like Java and C++.
The choice of authentication approach depends on the project requirements and infrastructure. Using a backend API with JWT authentication is a common approach for microservices architecture and follows modern trends. Services like Google Firebase Authenticator provide centralized authentication, especially for companies already using Google Cloud Platform (GCP). However, relying on cloud-based authentication services can create additional work if the company decides to switch cloud providers.
React Native: In my experience, I found Redux and Redux Toolkit to be the most effective for managing global and specific states in React Native projects. Storing session data in storage is generally discouraged from an information security perspective.
Flutter: In Flutter, finding reliable and well-functioning libraries can be challenging. Each additional library or dependency adds to the project's responsibility, requiring continuous updates and compatibility considerations. It's worth noting that in some cases, coding on top of the native Android or iOS projects may be necessary for specific features and gestures, as they differ between platforms.
Offline First: Ensuring App Functionality Without Internet Connection
Before discussing my experiences with specific databases, it's important to understand the concept of "Offline First." The Offline First approach prioritizes designing applications to work seamlessly even without an internet connection. This is particularly crucial for mobile applications, as network connectivity can be unreliable or unavailable in certain situations. By adopting an Offline First strategy, developers aim to provide a smooth user experience regardless of the user's online or offline status.
Now, let's explore two databases that facilitate offline functionality in mobile apps:
In one of my projects, I implemented the "Offline First" approach using WatermelonDB. WatermelonDB is a database designed specifically for offline data synchronization in mobile and web applications. It enables developers to store data locally on the mobile device, allowing the app to function smoothly even when there is no internet connection available. WatermelonDB provides a performant and reliable solution for offline data persistence, ensuring that users can access and modify data even in offline scenarios.
Real-Time Database from Google Cloud Platform (GCP)
In another project, I leveraged the Real-Time Database from Google Cloud Platform to achieve offline functionality. The Real-Time Database enables real-time synchronization of data across multiple devices. This means that changes made by one user on their device are instantly reflected on other devices connected to the same database, even without an active internet connection. The Real-Time Database from GCP proved to be valuable for applications that require immediate data updates and seamless collaboration among users, regardless of their online or offline status.
By utilizing these databases, I was able to incorporate offline functionality into my mobile applications, providing users with uninterrupted access to important data and ensuring a positive user experience even when an internet connection is not available.
Native Development with Kotlin and Swift in Hybrid Codebases
In hybrid mobile app development, where a single codebase is shared across multiple platforms, there are instances when it becomes necessary to incorporate native code using platform-specific languages like Kotlin and Swift. Here's an explanation of when and why native coding is required in hybrid codebases:
Certain tasks, such as complex animations, heavy computations, or accessing low-level device features, may require maximum performance and efficiency. In such cases, leveraging the native capabilities and performance optimizations offered by Kotlin for Android and Swift for iOS can deliver superior results compared to cross-platform solutions.
Platform-Specific UI Components
Each platform has its unique set of UI components and design guidelines. While cross-platform frameworks often provide a wide range of UI components, there might be instances where platform-specific UI elements are necessary to achieve a native look and feel. By utilizing Kotlin and Swift, developers can directly implement platform-specific UI components to deliver a seamless and consistent user experience.
Accessing Platform-Specific APIs
Certain platform-specific APIs, such as camera access, push notifications, or in-app purchases, may not be fully exposed or supported by cross-platform frameworks. In such cases, integrating Kotlin or Swift code allows developers to directly interact with these APIs and take advantage of the latest platform features, ensuring a richer app experience for users.
Code Reusability and Maintenance
Despite the benefits of native coding, it's important to maintain code reusability and minimize code duplication in hybrid codebases. Careful consideration should be given to identify areas where platform-specific code is truly necessary while keeping the majority of the codebase shared and platform-agnostic. This approach helps reduce maintenance efforts and ensures efficient collaboration among developers working on different platforms.
Bridging Native and Hybrid Code
To incorporate Kotlin or Swift code into a hybrid codebase, developers can utilize platform-specific bindings or bridges provided by cross-platform frameworks. These bridges act as intermediaries, allowing communication between the native and hybrid layers of the app. By effectively bridging the gap between native and hybrid code, developers can seamlessly integrate platform-specific functionality while maintaining a unified codebase.
Leveraging Native Libraries and SDKs
Another advantage of incorporating native code is the ability to leverage existing native libraries and SDKs. Kotlin and Swift have extensive ecosystems with numerous third-party libraries and APIs that can enhance app functionality. By tapping into these resources, developers can benefit from the rich ecosystem of native tools and solutions available for their respective platforms.
When carefully planned and implemented, the incorporation of Kotlin and Swift code in hybrid codebases can offer the best of both worlds: code reusability and platform-specific optimizations. It allows developers to achieve high-performance, platform-native experiences while maximizing code sharing and minimizing maintenance efforts.
Pipelines and DevOps for Mobile App Development
Mobile app development often involves multiple teams and a continuous integration and delivery (CI/CD) pipeline to streamline the development, testing, and deployment processes. Implementing DevOps practices can significantly improve the efficiency and quality of mobile app development. Here's an overview of the pipeline and DevOps considerations:
Source Code Management
Using a version control system (VCS) like Git is essential for mobile app development. It allows multiple developers to work collaboratively, tracks changes, and enables effective code management. Common practices include creating feature branches, enforcing code review, and using Git workflows such as GitFlow.
Automating the build process ensures that app builds are consistent and reproducible across different environments. Tools like Gradle for Android and Fastlane for iOS provide powerful automation capabilities, allowing you to define build configurations, manage dependencies, and handle code signing and provisioning profiles.
Continuous Integration (CI)
CI involves automatically building and testing the app whenever changes are pushed to the repository. This ensures early detection of issues and facilitates a faster feedback loop. Popular CI platforms like Jenkins, Travis CI, and CircleCI can be integrated with your VCS to trigger builds, run tests, and generate reports.
Automated testing plays a vital role in mobile app development to catch bugs, ensure functionality, and maintain a high-quality user experience. Various types of tests, including unit tests, integration tests, and UI tests, can be automated using frameworks like XCTest for iOS and Espresso for Android. Continuous testing can be integrated into the CI pipeline to run tests automatically on each code change.
Deployment and Delivery
Efficient deployment and delivery processes are critical for releasing updates and new features to users. Mobile app deployment involves distributing app builds to internal testers, beta testers, and eventually, the public. Tools like Firebase App Distribution, TestFlight, and Google Play Console provide features for managing different deployment tracks and gathering user feedback.
Continuous Deployment (CD)
Continuous deployment automates the release of app updates to app stores or over-the-air (OTA) distribution channels. It ensures a fast and seamless delivery process, reducing time-to-market and enabling rapid iterations. CD pipelines can integrate with app store publishing APIs, code signing mechanisms, and release management platforms to streamline the deployment workflow.
Monitoring and Analytics
Monitoring app performance and user behavior is crucial for making data-driven decisions and identifying potential issues. Incorporating mobile analytics tools like Firebase Analytics, Google Analytics, or custom analytics solutions can provide valuable insights into app usage, crashes, and user engagement. Monitoring solutions can also help track app performance, server-side issues, and API dependencies.
By implementing a well-defined CI/CD pipeline and leveraging DevOps principles, mobile app development teams can streamline collaboration, automate repetitive tasks, and deliver high-quality apps more efficiently.
Dependency Management: Balancing and Considerations for Low-Performance Devices
When developing mobile applications, it's common to rely on third-party libraries and dependencies to enhance functionality, streamline development, and save time. However, it's crucial to carefully consider the impact of these dependencies, particularly when it comes to running the app on low-performance devices. Here are some key points to keep in mind:
Evaluate the Necessity
Before adding a new dependency to your project, it's essential to evaluate its necessity. Consider whether the functionality provided by the dependency is critical to your app's core features and if it significantly improves the user experience. If the dependency isn't essential or doesn't contribute significantly to your app's value proposition, it may be worth reconsidering its inclusion, especially if it could potentially impact performance on low-end devices.
Impact on App Size and Resources
Every dependency added to your project increases the overall app size and resource utilization. This can have a noticeable impact on low-performance devices with limited storage and processing power. Evaluate the size of the dependency and its resource requirements to ensure they are within acceptable limits for your target devices. Minimizing the size and resource footprint of your app can help improve its performance on low-end devices.
Certain dependencies may introduce additional overhead and performance bottlenecks, especially if they rely on resource-intensive operations or complex algorithms. It's crucial to thoroughly test the app's performance with the added dependencies, particularly on low-performance devices. Monitor factors such as app launch time, responsiveness, and overall smoothness of the user interface. If performance degradation is observed, you may need to optimize or find alternative solutions to mitigate the impact on low-end devices.
Compatibility and Versioning
Dependencies can also introduce compatibility issues, especially when different libraries rely on conflicting versions of shared components. It's important to carefully manage dependency versions and resolve any conflicts that may arise. Additionally, consider the compatibility of the dependencies with your target devices and operating system versions. Ensure that the libraries you choose are optimized for low-performance devices and have a track record of stable performance.
User Experience Trade-offs
Adding dependencies to your project should always be a trade-off between functionality and performance. While certain dependencies may provide valuable features, they can also introduce complexity and potential performance issues. It's crucial to strike a balance between the desired functionality and the performance requirements of your target devices. Consider whether the added features justify the potential trade-offs in terms of app performance, especially on low-performance devices.
Testing on Low-Performance Devices
To gain a comprehensive understanding of how your app performs on low-end devices, it's important to conduct thorough testing on such devices during the development process. This will help identify any specific performance issues related to the added dependencies and enable you to optimize or make informed decisions about their inclusion in your project.
By carefully considering the necessity, impact on app size and resources, performance implications, compatibility, and user experience trade-offs of adding dependencies, you can make informed decisions that prioritize the performance and functionality of your app, even on low-performance devices.
Build and Publication Process: Getting Your App into App Stores
Once the development of your mobile app is complete, the next crucial step is to prepare it for distribution by building and publishing it in the respective app stores. Here's an overview of the process and considerations involved:
- Google Play Store To publish your app on the Google Play Store, you'll need a Google Play Developer account. Your employer or team lead should have this account set up. Here are the key steps involved in the process:
App Registration: Your employer or team lead will need to register the app on the Play Console. This involves providing essential details such as app functionality, description, screenshots, and contact information. As the developer, you may be required to provide assets like app screenshots running on various Android devices.
App Build: When using React Native or Flutter, generating the necessary build files (APK or AAB) for the app is relatively straightforward. However, additional configurations, such as app version, description, and contact details, need to be specified in the build configuration files.
Submission: Once you have the build files ready, they can be uploaded to the Play Console. It's crucial to ensure the build files are thoroughly tested before submission. The Play Console provides guidelines and requirements to ensure your app meets quality standards and follows app store policies.
- Apple App Store Publishing an app on the Apple App Store requires an Apple Developer account and access to a macOS device. Here are the general steps involved: App Registration: Your employer or team lead should create an Apple Developer account and provide you with the necessary access. In the Apple Developer portal, you'll need to register your app, including details such as app functionality, description, screenshots, and contact information.
App Build: To generate the necessary IPA file for iOS, you'll need a macOS device. The app build process typically involves configuring certificates, provisioning profiles, and other necessary settings. Familiarity with Xcode and Swift is beneficial when dealing with Apple's requirements and technologies.
Submission: After successfully building the app, you can submit it to the App Store using App Store Connect, Apple's platform for managing app submissions. Similar to the Play Store, thorough testing and adherence to Apple's guidelines are essential.
It's worth mentioning that both Google Play Store and Apple App Store have their own review processes and guidelines. The review process ensures that apps meet quality standards, adhere to policies, and are free from malicious content.