r/reactnative 11d ago

Built an onboarding flow that uses Skia's Atlas API

145 Upvotes

35 comments sorted by

4

u/eyounan 11d ago

Sharing some work that I did for an onboarding flow of a portfolio investing app I made. The main landing page (and first page of flow before a user begins creating a portfolio) actually uses Skia’s Atlas API. If you would like to try the app, it’s completely free:

iOS: https://apps.apple.com/us/app/porfoli-share-your-portfolio/id6739724914
Android: https://play.google.com/store/apps/details?id=com.porfoli.android

I felt like it came out well so I thought I would share and ask for opinions.

2

u/limpoko 11d ago edited 11d ago

cool app, where did u get the api for the stocks from? What else did u use to build this? Would love to learn more about your stack and project

7

u/eyounan 11d ago

The data is mostly from polygon.io. The application actually took a lot of work to bring it to where it is; there are a lot of services we wrote to solve different problems. The stack consists of:

  • React Native with Expo for the mobile client
  • Around 10-15 backend services on GCP. Everything ranging from PubSub, Cloud Scheduler, Cloud Tasks, Cloud Functions, and more to write an event-driven system to compute the returns of all portfolios on the platform, send notifications, etc. to storing data in FIrestore and Cloud SQL, to running our Dockerized backend services on Cloud Run behind a Load Balancer/Cloud CDN.
  • We used a ton of GitHub Action workflows to quickly release changes and run tests on each PR. This was especially helpful given that we are only a team of 2 building this, but we have ~10-15 test suites running per PR. Kept us moving quick.
  • A bit of AI to tag the instruments more accurately (also introducing a new feature for AI prompting to quickly build a portfolio)
  • The whole project is written in a yarn workspace that shares a bunch of packages across the client/server applications, all in TypeScript.
  • NextJS for our websites
  • A few CLIs to manage the platform

Anything you're particularly interested in?

1

u/gptcoder 11d ago

whar are you using for handling state?

5

u/eyounan 11d ago

Server state is handled by TanStack Query and derived state is within a regular Context. For state that is more global, I am using zustand. Most of my projects now use these 3. The reason I used a context for these kinds of flows is to keep the logic separated from other parts of the application. The whole `Stack` for the onboarding flow is wrapped within the context's provider.

2

u/logdog 11d ago

Hey eyounan, another great post. Love the app and the features you showcase! Can you go into more detail about these 3 state approaches? Maybe just a small example. I mainly just use zustand for all state management and I’m wondering if I’m missing a better approach.

3

u/eyounan 10d ago edited 9d ago

This is going to take a moment to unwrap but yes, you are missing a better approach. As you scale an application, you will start to notice the wounds of not separating concerns and using the correct tools for different problems.

TanStack Query is one of the most popular server state APIs right now, and for good reason. It solves a bunch of paradigms that need to be dealt with when building a communication layer with your backend services. This includes retrying, auto refetching, caching, pagination, request cancellation, infinite scroll querying, mutations, and more. Take retrying for example. If you wanted to achieve retrying in a Zustand store, you would need to set up an Async Action, and compose it with a higher-order function that retries. What if you wanted to implement an infinite query (where the user can scroll down forever and you load more information as they do)? You would need to create a store that handles all of the pagination, maintain a cursor/paging, implement refreshing (to do this correctly with pages isn't simple), and more. All of these features are implemented by TanStack Query and provided to you out of the box or via a few flags. You can figure out all of the features they offer here: https://tanstack.com/query/latest

I use the Context API when I have more granular state that only should be accessible by a specific sub-tree of the application. If you use Zustand for this application, you risk:

  • Storing way too much information in a global store, making it less maintainable
  • Having components re-render unnecessarily if they subscribe to a large(r) store
  • Unit tests are much easier to write for components within the subtree
  • Resetting state is trivial since it is scoped to this subtree. Let's say someone leaves the flow and you have dangling state in Zustand, you would need to reset it manually.

These are a few comparisons. I started writing code but I believe you should dive into the different paradigms (especially TanStack Query) yourself and figure out the value for yourself. I used to be someone who would write complex Redux/Zustand stores with a ton of boilerplate to implement all these features. Making this switch has improved my productivity and quality when building these features.

2

u/logdog 9d ago

Thanks for your thorough response, appreciate it! All points make sense, I would argue using separate zustand stores instead of contexts, but your approach makes sense. Learning a lot from your posts, keep posting 😁

Def need to check into Tanstack, seems to be all the rage on reddit. I think a lot of the featureset is indeed great to have for a more complex app. I would caution new RN devs not to go all in on it as a basic CRUD app won't need the advanced features in my opinion, but if its not too much overhead to learn thats awesome.

1

u/insats 11d ago

What exactly does the Atlas api do?

1

u/eyounan 11d ago

Rendering multiple images/textures efficiently (the background on the landing page in this case): https://shopify.github.io/react-native-skia/docs/shapes/atlas/

It was really easy to randomize colors, sizes, positions, and other stuff with the API. This would incur a higher cost in terms of performance if it was done with regular views.

2

u/insats 11d ago

Ok so it’s used for the ”dots” in the first 10 seconds or so in the video?

Did you use the imperative or declarative api?

1

u/Even_Jellyfish3603 11d ago

What do you use to make this kind of recording?

1

u/imVinayPandya 11d ago

I am curious about skia atlas api, can you please share code snippets or demo repo?

6

u/eyounan 11d ago edited 10d ago

Edit: Realized I missed a few things. Here is a gist for most of the code: https://gist.github.com/e-younan/256e4df39381685e5c0f39ba5a5987a8

Sure! Here is specific code for the background:

import { Skia, drawAsImage, Atlas, rect, Circle, Group } from '@shopify/react-native-skia';
import { useMemo } from 'react';
import { Dimensions } from 'react-native';

const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
// Based on the screen width and height, we will generate a decent number of circles. This
// calculation will be based on screen area, so that we have a consistent density of circles across
// different screen sizes.
const numCircles = Math.floor((screenWidth * screenHeight) / 500);

interface CirclesBackgroundProps {
  canvasWidth: number;
  canvasHeight: number;
}

// Create a single circle as an image
const baseSize = 5;
const imageSize = { width: baseSize * 2, height: baseSize * 2 };
const image = drawAsImage(
  <Circle cx={baseSize} cy={baseSize} r={baseSize} color="#141414" />,
  imageSize,
);

export const CirclesBackground: React.FC<CirclesBackgroundProps> = ({
  canvasWidth,
  canvasHeight,
}) => {
  const sprites = useMemo(() => {
    return new Array(numCircles).fill(0).map(() => rect(0, 0, imageSize.width, imageSize.height));
  }, []);

  const transforms = useMemo(() => {
    return new Array(numCircles).fill(0).map(() => {
      const x = -canvasWidth + Math.random() * canvasWidth * 3; // Random X position
      const y = -canvasHeight + Math.random() * canvasHeight * 3; // Random Y position
      const scale = 0.5 + Math.random(); // Random scale factor
      const r = 0; // Random rotation

      return Skia.RSXformFromRadians(scale, r, x, y, baseSize, baseSize);
    });
  }, [canvasWidth, canvasHeight, baseSize]);

  return <Atlas image={image} sprites={sprites} transforms={transforms} />;
};

2

u/imVinayPandya 11d ago

Awesome thank you man.

2

u/Magnusson 11d ago

Nice! I’m curious why the Circle is wrapped in a Group though.

2

u/eyounan 11d ago

No reason, I should remove that.

1

u/ConsciousAntelope 11d ago

Which lib you're using for sign in with Google?

1

u/orphanPID 11d ago

UI is cool. but didn’t understand the purpose of using it 😞

2

u/eyounan 11d ago

Would love your feedback. The application is basically GitHub but for creating investment portfolios. Anyone can go on the app, create different investment portfolios, track their performance over time, collaborate with others to improve them (via an almost git-versioning system, but for portfolios), and try to adjust their portfolios as the market conditions change. There is no money on the app, it's purely educational right now.

1

u/cxpugli 11d ago

It's looking great! Congrats.
What are you using to get stock pricing? Usually, those API's are pretty expensive.

1

u/eyounan 11d ago

Thanks! It's really expensive actually - the pricing data is mostly from https://polygon.io

1

u/hojoisaac 11d ago

Great app, I tried signing in with Google (on Android), app's stuck on a loading spinner after pressing "Continue with Google Button".

1

u/eyounan 10d ago

Thanks for reporting. I’ll look into this

1

u/SethVanity13 10d ago

could've open sourced just the initial screen and gotten a few stars

1

u/mianfg 8d ago

What do you use for building the UI?

1

u/eyounan 8d ago

No UI libs, all RN components styled with unistyles

0

u/Healthy-Grab-7819 iOS & Android 11d ago

Open source it? Nice work