2

I am using react native to create a drawing app using react native skia and react native gesture handler, this is my code for that screen

import React, { useState } from "react";
import { View, Text } from "react-native";
import {
  Gesture,
  GestureDetector,
  GestureHandlerRootView,
  PanGestureHandler,

} from "../node_modules/react-native-gesture-handler";
import { Canvas, Path } from "@shopify/react-native-skia";



export default function Draw() {
  const [paths, setPaths] = useState([[]]);

  const pan = Gesture.Pan()
    .onStart((g) => {
      const newPaths = paths;
      newPaths.push(["M " + g.x + " " + g.y]);
      setPaths(newPaths);

    })
    .onUpdate((g) => {
        const newPaths = paths;
        newPaths[newPaths.length-1].push("L " + g.x + " " + g.y);
        setPaths(newPaths);
    })
    .minDistance(1)

  return (
       <GestureDetector gesture={pan}>
          <Canvas style={{flex:1}} collapsable={false}>
        {paths.map((p, index) => (
            
              <Path
                key={index}z
                path={p.join(" ")}
                strokeWidth={5}
                style="stroke"
                color={"black"}
              />
            ))}
          </Canvas>
      </GestureDetector>
  );
}

This is my App.js

import React, { Component } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import ScoutingSheet from './screens/ScoutingSheet';
import QRCodeScanner from './screens/QRCodeScanner';
import Draw from './screens/AutonCanvas';
import {
  GestureHandlerRootView,
} from "./node_modules/react-native-gesture-handler";
const Stack = createNativeStackNavigator();

export default class App extends Component {

  // Initialize Firebase
  render() {
    return (
      <GestureHandlerRootView style={{flex:1}}>
      <NavigationContainer>
        <Stack.Navigator screenOptions={{
          headerShown: false,
        }}>
          
          <Stack.Screen name="AutonCanvas" component={Draw} />

          <Stack.Screen name="ScoutingSheet" component={ScoutingSheet} />

          <Stack.Screen name="QrCodeScanner" component={QRCodeScanner} />



        </Stack.Navigator>
      </NavigationContainer>
      </GestureHandlerRootView>

    );
  }
}

This is my package.json

{
  "name": "roboticsscoutingapp",
  "version": "1.0.0",
  "scripts": {
    "start": "expo start --dev-client",
    "android": "expo run:android",
    "ios": "expo run:ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "@react-native-async-storage/async-storage": "~1.17.3",
    "@react-native-community/cli-platform-android": "^10.2.0",
    "@react-navigation/native-stack": "^6.9.1",
    "@shopify/react-native-skia": "0.1.172",
    "expo": "^48.0.0",
    "expo-barcode-scanner": "~12.3.2",
    "expo-splash-screen": "~0.18.1",
    "expo-status-bar": "~1.4.0",
    "expo-updates": "~0.16.3",
    "i": "^0.3.7",
    "npm": "^9.6.1",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-native": "0.71.3",
    "react-native-gesture-handler": "^2.9.0",
    "react-native-paper": "^4.12.5",
    "react-native-qrcode-svg": "^6.2.0",
    "react-native-reanimated": "^2.3.0",
    "react-native-simple-radio-button": "^2.7.4",
    "react-native-svg": "13.4.0",
    "react-native-table-component": "^1.2.2",
    "react-native-web": "~0.18.7",
    "react-navigation": "^4.4.4"
  },
  "devDependencies": {
    "@babel/core": "^7.18.6"
  },
  "private": true
}

The problem is that, whenever I pan my finger on the screen, no drawing shows up, but when I refresh the app, the SVG image suddenly shows up in the correct place, is it a problem with my packages or something else?

I tried changing my react native gesture handler version but that did not fix anything, I also moved my GestureHandlerRoot to my App.js from my Draw function, where it was previously

1 Answers1

0

I think you are mutating paths. This probably leads to React not recognising any change.

Try const newPaths = [...paths]; instead of const newPaths = paths;

If this works we can conclude that mutation was the problem. But I also don't think it's a good idea to copy the array on every update (stroke).

I think it would be better to have a separate variable for currentPath. You could then push currentPath onto paths in the onEnd() event.

With a separate currentPath variable you need to render this path separately.

Pseudo code for what I mean:


const [currentPath, setCurrentPath] = getState([]);

const pan = Gesture.Pan()
    .onStart((g) => {
      setCurrentPath(["M " + g.x + " " + g.y]);
    })
    .onUpdate((g) => {
      setCurrentPath(...currentPath, "L " + g.x + " " + g.y);
    })
    .onEnd(() => {
      setPaths([...paths, currentPath]);
      setCurrentPath([]);
    })
    .minDistance(1)

In the render block, add a separate <Path> for currentPath.

<Path
   key={'currentPath'}
   path={currentPath.join(" ")}
   strokeWidth={5}
   style="stroke"
   color={"black"}
/>

N.B. I'm not sure if separating currentPath will do much optimization. Try without it first. I can imagin if paths holds massive amounts of paths, the array copying might take some time. But I'm actually guessing. Maybe Hermes or V8 is bonkers fast on this operation.

N.B.2 I don't know if the <Path> component can handle an empty path. If not you'll need to render it conditionally.

N.B.3 I'm not sure if there is an onEnd() event. But something alike surely exists.

Michael
  • 1,764
  • 2
  • 20
  • 36