Wednesday, May 7, 2025

React Native Parameter Passing

When building mobile apps with React Native, we often face a decision about how to pass data between components - especially to screen components. Let's examine two common approaches and when to use each:

1. Direct Props Passing:

interface AddressInfoProps {
    cartItems: CartItem[];
    discountPrice: number;
}
const AddressInfo: React.FC<AddressInfoProps> = ({ cartItems, discountPrice }) => {
    // Component implementation
};

2. Navigation Route Parameters:

import { useRoute, RouteProp } from '@react-navigation/native';
type AddressInfoRouteProp = RouteProp<{
    AddressInfo: {
        cartItems: CartItem[];
        discountPrice: number;
    };
}, 'AddressInfo'>;
const AddressInfo: React.FC = () => {
    const route = useRoute<AddressInfoRouteProp>();
    const { cartItems, discountPrice } = route.params;
    // Component implementation
};

Option 1 is simpler, but it lacks built-in mechanisms to persist state when navigating back and forth between screens. You must always explicitly pass the parameters, meaning the developer is responsible for state persistence. Use it when the component is part of a larger component hierarchy where the parent already has the necessary data to pass down.

Use route parameters (Option 2) when the component represents a screen in your navigation flow—i.e., when backward and forward navigation is expected. With Option 2, the React Native navigation system handles state automatically. If the user navigates to another page and then presses the back button, the screen will re-render with its previous state.

Other options:

Context API: Best for data needed by many components at different nesting levels, but avoid for frequently changing data as it can cause unnecessary re-renders

State Management Libraries (Redux, MobX, Zustand, Jotai): Best for complex applications with lots of shared state and interactions between different parts of the app

URL Parameters: Best for web applications where maintaining bookmarkable state is important

Local Storage: Best for persisting data between app sessions like login status

Monday, May 5, 2025

Storing auth tokens on mobile

React Native AsyncStorage is a simple key-value storage system that saves data as plain text. This means that anyone with access to the device or its backups can potentially read your auth tokens, API keys, or other sensitive data if you use AsyncStorage.

Storing sensitive data unencrypted can also violate Google Play and AppStore guidelines which might result in your app being rejected. Reviewers might run basic security tests that could expose unencrypted token storage.

SecureStore is Expo's abstraction layer that leverages the native security infrastructure of both iOS and Android platforms. SecureStore automatically encrypts all data before storage and provides a unified API that works identically on both iOS and Android. While SecureStore has slightly more overhead due to encryption/decryption operations, the performance impact is negligible for typical use cases like token storage. The security benefits far outweigh any minor performance considerations.

Wednesday, March 5, 2025

Converting a web app to mobile

If you have a responsive web app, you could convert it to a mobile app using React Native WebView without writing any additional mobile code. However, there may be challenges with getting approval from both Google Play and the Apple App Store when using a WebView wrapper approach:
  1. Apple App Review Guidelines Rule 4.2 explicitly states that apps should include features, content, and UI elements that elevate them beyond being just a repackaged website.
  2. Google Play Store: While generally more lenient than Apple, Google still requires apps to provide a reasonably engaging user experience.
The more native integration you include, the more likely you are to pass the review process. Potential Solutions:
  1. Add native functionality such as push notifications or device camera integration.
  2. Implement offline capabilities that aren't available in the web version.
The easiest way to convert your web app to mobile is to ask claude.ai to do it. Your job would be to review, refactor, test and debug the generated mobile code.

Monday, February 10, 2025

Memory cost of image conversion

In my web app, users can upload images as PNG, JPEG etc. I convert them to WebP because it uses much less space on disk and loads faster in web page requests. However, conversion uses raw pixels, so the memory (RAM) required is almost irrelevant to the original file size because file size depends on the compression of raw pixels. The memory needed is primarily determined by: Width * Height * Bytes per pixel. 

A PNG needs 3 bytes for RGB and 1 byte for alpha = 4 bytes per pixel. For a 5637x5637 PNG with RGBA colors, we need 5637 * 5637 * 4 bytes per pixel = ~127MB just for the uncompressed pixel data alone. The file size of that PNG is 6MB.

JPEG files typically don't have an alpha channel, so they need 3 bytes per pixel. For a 5637x5637 JPEG, memory needed would be 5637 * 5637 * 3 = ~95MB. The actual file size of the JPEG could range from:

  • High quality (90%): 2-8MB
  • Medium quality (70%): 1-4MB
  • Low quality (50%): 500KB-2MB
When setting upload limits for image processing, you should primarily consider the image dimensions (megapixels) rather than the file size (megabytes). Here's why:

  1. A user could upload a highly compressed 2MB PNG that's 10000x10000 pixels - this would need ~400MB RAM to process.
  2. Another user could upload a poorly compressed 10MB JPEG that's 1000x1000 pixels - this would only need ~3MB RAM to process.

Considering that current mobile cameras can typically capture 16MP images, the decompressed image would require approximately 16e6 pixels × 4 bytes/pixel / 1024 / 1024 = 61MB of memory. Additional couple of MB memory may be needed as a buffer for libraries like PHP GD, which might use it for copying bytes. 

Here are details from my web app of processing a 16MP JPEG with GD functions (total 172MB, calculated with memory_get_peak_usage):

  • imagecreatefromjpeg: +68MB
  • imagewebp: 130MB (+62MB)
  • resizeWebP:
    • imagecreatefromwebp: 140MB (+10MB)
    • imagecreatetruecolor: 156MB (+16MB)
    • imagewebp: 172MB (+16MB)

To be safe, allocating 200MB of memory would be prudent. If your server has 3GB total RAM, it means that your web app can handle at most 3GB/200MB ≈ 15 concurrent image conversions.

Converting images to WebP saves disk space and speeds up the web app in the long run, but it consumes a lot of RAM in the short run.

Monday, December 23, 2024

Why Software Project Estimation Is Difficult

In my experience, the following factors make software project effort/cost estimation difficult:

  1. It is much easier to estimate for the happy path which leaves gaps in specifications for logging, handling edge/error/timeout cases and exceptions.
  2. It is common to overlook non-functional requirements like performance, security, copy protection, scalability. They often become the reason a project fails at scale, even if it succeeds at first. 
  3. Requirement/design changes due to new information after first user (alpha/beta) tests.
  4. Not taking into account code clean up effort as the project complexity increases.
  5. Changes in libraries and frameworks at least once a year, necessitating unplanned rework.

Every if, switch, or loop condition can introduce new paths through the code. If a function has n independent Boolean conditions, the number of possible paths is up to 2^n. If a function takes multiple parameters, the combinations of edge values increase multiplicatively. 1 input has usually at least 3 edge cases, n inputs would have at least 3^n edge case combinations. If the function depends on or alters internal state, interacts with external systems, or uses asynchronous logic, you must test for edge timing, race conditions, resource exhaustion, etc. Edge cases scale faster than linearly with added logic, often closer to exponential or combinatorial growth depending on interaction depth.

Handling error cases and edge conditions often takes much more effort than the happy path due to the need for additional logic, testing, and debugging. If edge cases are not considered early, addressing them later can introduce costly redesign efforts. Unfortunately, the number and complexity of edge cases grow as the project progresses, especially if new scenarios are discovered during development or testing. Handling one error case might introduce or expose others, creating a chain of additional considerations that were not part of the original estimate.

Neglecting regular code cleanup leads to the accumulation of technical debt, which increases the time and effort required to implement similar features later in the project. New developers joining the team may struggle to understand and contribute to the codebase, further slowing development. Additionally, the effort required to fix bugs, integrate new systems, or perform upgrades can grow exponentially over time.

APIs, libraries and frameworks are typically updated every six months, and programming languages undergo significant changes every few years. While these updates bring improvements, they can impact project timelines.

All these factors can make your initial effort/time estimation 10 times less than the actual cost in the end. A rule of thumb you can use is to estimate effort/time for the happy path and multiply that by 10 to get a realistic number.

Monday, December 2, 2024

Interpreted vs compiled programming languages

With an interpreted language like Python or PHP, if you notice a bug in the web app API logic or the UI script, you can fix it and test the change immediately. This is much faster than stopping to recompile and deploy every time, as would be required in a compiled language.

Without compiled binaries, the same source code can be deployed on multiple platforms without adjustments, as long as the runtime environment is consistent.

Many modern web frameworks (e.g., Flask, Laravel Vite) have features like "hot reloading," which automatically detect changes in code and reload the application without restarting. This is much easier to implement in interpreted environments.

While these advantages are significant, they may come at the cost of runtime performance and error detection, as compilation often catches errors early. However, for most web applications, the increased development speed and flexibility usually outweigh these trade-offs.

Music: The Cardigans - My Favorite Game

Friday, November 29, 2024

Base64 encoding

Base64 encoding ensures that the output only contains characters from a specific, limited set of 64 characters, which are: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

It is safe for most text-based systems because none of the potentially problematic characters (\, \0, \n, \r, \x1a, ', ") appear in the output. Note that \x1a is the hexadecimal representation of the ASCII control character SUB (substitute). It is a non-printable character with the decimal value 26 in the ASCII table. If included in a text string, \x1a is typically invisible and may disrupt processing, especially in legacy systems that interpret it as EOF.

Example:

Base64 encoding increases the size of the input data by approximately 33%. Specifically: For every 3 bytes of input, base64 adds 4 characters. For example if input JSON is {"customer_id":123,"email":"user@example.com","nonce":"d1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6"}, which is 94 characters, the output will be eyJjdXN0b21lcl9pZCI6IDEyMywgImVtYWlsIjogInVzZXJAZXhhbXBsZS5jb20iLCAibm9uY2UiOiAiZjVkMGMzZGY3ZTM3ODQ2NWQ0NjhkMTdjZTRhNGNlMzIifQ==, which is 128 characters. Note the "==" characters at the end. These are used for padding which ensures the length of the encoded string is a multiple of 4. If your database column to hold the token is VARCHAR(255), assuming a max customer_id of "999 999 999", max email size should not exceed 111 characters.

Email max lengths [RFC 5321, Simple Mail Transfer Protocol]:

  • Local part (before the @): Up to 64 characters (octet = byte).
  • Domain part (after the @): Up to 255 characters.

Total length: The maximum length of a valid email address is 320 characters, but this is extremely rare in practice.

Music: Barış Manço - Dönence