react-native-skia icon indicating copy to clipboard operation
react-native-skia copied to clipboard

[Android] App crash in release build works fine in debug build

Open bluntbrain opened this issue 1 year ago • 3 comments

Description

Logs ---------------------------- PROCESS STARTED (27444) for package com.couplapp ---------------------------- 2023-08-09 19:58:54.086 27442-27442 DEBUG crash_dump64 A Cmdline: com.couplapp 2023-08-09 19:58:54.087 27442-27442 DEBUG crash_dump64 A pid: 26928, tid: 26928, name: com.couplapp >>> com.couplapp <<< 2023-08-09 19:58:54.087 27442-27442 DEBUG crash_dump64 A #00 pc 0000000000065694 /data/app/~~z7gaWc39l7ruURuChwu_Jw==/com.couplapp-Dq-SH5U1RGI0vq66qWdGeQ==/lib/arm64/libreanimated.so (BuildId: 4e2ea9c5275a9c49b5fdd425d29436cffd96a7b0) 2023-08-09 19:58:54.087 27442-27442 DEBUG crash_dump64 A #01 pc 0000000000080a78 /data/app/~~z7gaWc39l7ruURuChwu_Jw==/com.couplapp-Dq-SH5U1RGI0vq66qWdGeQ==/lib/arm64/libreanimated.so (reanimated::Scheduler::triggerUI()+240) (BuildId: 4e2ea9c5275a9c49b5fdd425d29436cffd96a7b0) 2023-08-09 19:58:54.087 27442-27442 DEBUG crash_dump64 A #02 pc 0000000000084098 /data/app/~~z7gaWc39l7ruURuChwu_Jw==/com.couplapp-Dq-SH5U1RGI0vq66qWdGeQ==/lib/arm64/libreanimated.so (facebook::jni::detail::MethodWrapper<void (reanimated::AndroidScheduler::)(), &(reanimated::AndroidScheduler::triggerUI()), reanimated::AndroidScheduler, void>::dispatch(facebook::jni::alias_ref<facebook::jni::detail::JTypeFor<facebook::jni::HybridClass<reanimated::AndroidScheduler, facebook::jni::detail::BaseHybridClass>::JavaPart, facebook::jni::JObject, void>::_javaobject>)+44) (BuildId: 4e2ea9c5275a9c49b5fdd425d29436cffd96a7b0) 2023-08-09 19:58:54.087 27442-27442 DEBUG crash_dump64 A #03 pc 0000000000084004 /data/app/~~z7gaWc39l7ruURuChwu_Jw==/com.couplapp-Dq-SH5U1RGI0vq66qWdGeQ==/lib/arm64/libreanimated.so (facebook::jni::detail::FunctionWrapper<void ()(facebook::jni::alias_ref<facebook::jni::detail::JTypeFor<facebook::jni::HybridClass<reanimated::AndroidScheduler, facebook::jni::detail::BaseHybridClass>::JavaPart, facebook::jni::JObject, void>::_javaobject>), facebook::jni::detail::JTypeFor<facebook::jni::HybridClass<reanimated::AndroidScheduler, facebook::jni::detail::BaseHybridClass>::JavaPart, facebook::jni::JObject, void>::_javaobject*, void>::call(_JNIEnv*, _jobject*, void ()(facebook::jni::alias_ref<facebook::jni::detail::JTypeFor<facebook::jni::HybridClass<reanimated::AndroidScheduler, facebook::jni::detail::BaseHybridClass>::JavaPart, facebook::jni::JObject, void>::_javaobject>))+60) (BuildId: 4e2ea9c5275a9c49b5fdd425d29436cffd96a7b0) 2023-08-09 19:58:54.087 27442-27442 DEBUG crash_dump64 A #04 pc 0000000000082e88 /data/app/~~z7gaWc39l7ruURuChwu_Jw==/com.couplapp-Dq-SH5U1RGI0vq66qWdGeQ==/lib/arm64/libreanimated.so (facebook::jni::detail::MethodWrapper<void (reanimated::AndroidScheduler::)(), &(reanimated::AndroidScheduler::triggerUI()), reanimated::AndroidScheduler, void>::call(_JNIEnv, _jobject*)+36) (BuildId: 4e2ea9c5275a9c49b5fdd425d29436cffd96a7b0) 2023-08-09 19:58:54.088 27442-27442 DEBUG crash_dump64 A #05 pc 0000000000073a54 /data/app/~~z7gaWc39l7ruURuChwu_Jw==/com.couplapp-Dq-SH5U1RGI0vq66qWdGeQ==/oat/arm64/base.odex (art_jni_trampoline+116) 2023-08-09 19:58:54.088 27442-27442 DEBUG crash_dump64 A #07 pc 0000000000347626 /data/app/~~z7gaWc39l7ruURuChwu_Jw==/com.couplapp-Dq-SH5U1RGI0vq66qWdGeQ==/oat/arm64/base.vdex (com.swmansion.reanimated.Scheduler$1.run+28) 2023-08-09 19:58:54.088 27442-27442 DEBUG crash_dump64 A #09 pc 000000000034764e /data/app/~~z7gaWc39l7ruURuChwu_Jw==/com.couplapp-Dq-SH5U1RGI0vq66qWdGeQ==/oat/arm64/base.vdex (com.swmansion.reanimated.Scheduler$2.runGuarded+12) 2023-08-09 19:58:54.088 27442-27442 DEBUG crash_dump64 A #10 pc 00000000000a9e94 /data/app/~~z7gaWc39l7ruURuChwu_Jw==/com.couplapp-Dq-SH5U1RGI0vq66qWdGeQ==/oat/arm64/base.odex (com.facebook.react.bridge.GuardedRunnable.run+52) 2023-08-09 19:58:54.171 4887-4887 audit auditd E type=1701 audit(1691591334.167:65904): auid=4294967295 uid=10448 gid=10448 ses=4294967295 subj=u:r:untrusted_app_30:s0:c192,c257,c512,c768 pid=26928 comm="com.couplapp" exe="/system/bin/app_process64" sig=11 res=1 2023-08-09 19:58:54.452 5403-5445 WindowManager system_server E win=Window{1bf733c u0 com.couplapp/com.couplapp.MainActivity EXITING} destroySurfaces: appStopped=false cleanupOnResume=false win.mWindowRemovalAllowed=true win.mRemoveOnExit=true win.mViewVisibility=0 caller=com.android.server.wm.ActivityRecord.destroySurfaces:6529 com.android.server.wm.ActivityRecord.destroySurfaces:6510 com.android.server.wm.WindowState.onExitAnimationDone:5965 com.android.server.wm.ActivityRecord$$ExternalSyntheticLambda10.accept:2 java.util.ArrayList.forEach:1262 com.android.server.wm.ActivityRecord.onAnimationFinished:8569 com.android.server.wm.ActivityRecord.postApplyAnimation:6243 ---------------------------- PROCESS ENDED (26928) for package com.couplapp ----------------------------

Version

0.1.195

Steps to reproduce

"react": "18.1.0", "react-native": "0.70.6", "@shopify/react-native-skia": "^0.1.195",

Snack, code example, screenshot, or link to a repository

import {
  StyleSheet,
  Text,
  View,
  Dimensions,
  TouchableOpacity,
} from 'react-native';
import React from 'react';
import {curveBasis, line, scaleLinear, select} from 'd3';
import {
  Canvas,
  Circle,
  Group,
  LinearGradient,
  Path,
  Skia,
  useTouchHandler,
  useValue,
  vec,
  useComputedValue,
  PathVerb,
  Line,
} from '@shopify/react-native-skia';
import arrow from '../assets/images/greyArrowRight.png';
import {Image} from 'react-native';
import {getAdjacentMonth} from '../utils/lineGraphHelpers';
import {formatCurrency, log} from '../utils/helperFunctions';

const width = Dimensions.get('window').width * 1.08;
const height = 200;

const useGraphTouchHandler = (x, setValue) => {
  return useTouchHandler({
    onActive: e => {
      const lowerBound = width / 8 + 10;
      const upperBound = (width / 8) * 7;
      if (e.x > lowerBound && e.x < upperBound) {
        x.current = e.x;
        setValue(e.x);
      }
    },
  });
};

const Cursor = ({x, y}) => {
  const transform = useComputedValue(
    () => [{translateX: x.current}, {translateY: y.current}],
    [x, y],
  );
  return (
    <Group transform={transform}>
      <Circle cx={0} cy={0} r={8} color={'#244566'} />
      <Circle cx={0} cy={0} r={16} color={'#244566'} opacity={0.15} />
      <Line
        p1={vec(0, 0)}
        p2={vec(0, height)}
        color={'#244566'}
        strokeWidth={1}
      />
    </Group>
  );
};
const selectCurve = (cmds, x) => {
  let from = vec(0, 0);
  for (let i = 0; i < cmds.length; i++) {
    const cmd = cmds[i];
    if (cmd[0] === PathVerb.Move) {
      from = vec(cmd[1], cmd[2]);
    } else if (cmd[0] === PathVerb.Cubic) {
      const c1 = vec(cmd[1], cmd[2]);
      const c2 = vec(cmd[3], cmd[4]);
      const to = vec(cmd[5], cmd[6]);
      if (x >= from.x && x <= to.x) {
        return {
          from,
          c1,
          c2,
          to,
        };
      }
      from = to;
    }
  }
  return null;
};
const cubicBezier = (t, from, c1, c2, to) => {
  const term = 1 - t;
  const a = 1 * term ** 3 * t ** 0 * from;
  const b = 3 * term ** 2 * t ** 1 * c1;
  const c = 3 * term ** 1 * t ** 2 * c2;
  const d = 1 * term ** 0 * t ** 3 * to;
  return a + b + c + d;
};
const round = (value, precision = 0) => {
  const p = Math.pow(10, precision);
  return Math.round(value * p) / p;
};
const cuberoot = x => {
  'worklet';
  const y = Math.pow(Math.abs(x), 1 / 3);
  return x < 0 ? -y : y;
};
const cubicBezierYForX = (x, a, b, c, d, precision = 2) => {
  const pa = -a.x + 3 * b.x - 3 * c.x + d.x;
  const pb = 3 * a.x - 6 * b.x + 3 * c.x;
  const pc = -3 * a.x + 3 * b.x;
  const pd = a.x - x;
  const t = solveCubic(pa, pb, pc, pd)
    .map(root => round(root, precision))
    .filter(root => root >= 0 && root <= 1)[0];

  return cubicBezier(t, a.y, b.y, c.y, d.y);
};
const solveCubic = (a, b, c, d) => {
  if (Math.abs(a) < 1e-8) {
    // Quadratic case, ax^2+bx+c=0
    a = b;
    b = c;
    c = d;
    if (Math.abs(a) < 1e-8) {
      // Linear case, ax+b=0
      a = b;
      b = c;
      if (Math.abs(a) < 1e-8) {
        // Degenerate case
        return [];
      }
      return [-b / a];
    }

    const D = b * b - 4 * a * c;
    if (Math.abs(D) < 1e-8) {
      return [-b / (2 * a)];
    } else if (D > 0) {
      return [(-b + Math.sqrt(D)) / (2 * a), (-b - Math.sqrt(D)) / (2 * a)];
    }
    return [];
  }

  // Convert to depressed cubic t^3+pt+q = 0 (subst x = t - b/3a)
  const p = (3 * a * c - b * b) / (3 * a * a);
  const q = (2 * b * b * b - 9 * a * b * c + 27 * a * a * d) / (27 * a * a * a);
  let roots;

  if (Math.abs(p) < 1e-8) {
    // p = 0 -> t^3 = -q -> t = -q^1/3
    roots = [cuberoot(-q)];
  } else if (Math.abs(q) < 1e-8) {
    // q = 0 -> t^3 + pt = 0 -> t(t^2+p)=0
    roots = [0].concat(p < 0 ? [Math.sqrt(-p), -Math.sqrt(-p)] : []);
  } else {
    const D = (q * q) / 4 + (p * p * p) / 27;
    if (Math.abs(D) < 1e-8) {
      // D = 0 -> two roots
      roots = [(-1.5 * q) / p, (3 * q) / p];
    } else if (D > 0) {
      // Only one real root
      const u = cuberoot(-q / 2 - Math.sqrt(D));
      roots = [u - p / (3 * u)];
    } else {
      // D < 0, three roots, but needs to use complex numbers/trigonometric solution
      const u = 2 * Math.sqrt(-p / 3);
      const t = Math.acos((3 * q) / p / u) / 3; // D < 0 implies p < 0 and acos argument in [-1..1]
      const k = (2 * Math.PI) / 3;
      roots = [u * Math.cos(t), u * Math.cos(t - k), u * Math.cos(t - 2 * k)];
    }
  }

  // Convert back from depressed cubic
  for (let i = 0; i < roots.length; i++) {
    roots[i] -= b / (3 * a);
  }

  return roots;
};

const getYForX = (path, x, precision = 2) => {
  const cmds = path.toCmds();
  const c = selectCurve(cmds, x);
  if (c === null) {
    return 0;
  }
  return cubicBezierYForX(x, c.from, c.c1, c.c2, c.to, precision);
};

const months = {
  1: 'Jan',
  2: 'Feb',
  3: 'Mar',
  4: 'Apr',
  5: 'May',
  6: 'Jun',
  7: 'Jul',
  8: 'Aug',
  9: 'Sep',
  10: 'Oct',
  11: 'Nov',
  12: 'Dec',
};

const getGraphMonthValue = monthNo => {
  const month = monthNo % 100;
  const year = String(monthNo).split('').slice(0, 4).join('');

  return year * 100 + (month * 100) / 12;
};

export default function LineGraph({
  data = [],
  getPrevMonth = () => {},
  getNextMonth = () => {},
  isPrev = true,
  isNext = true,
}) {
  const makeGraph = () => {
    const edgedData = [
      {value: 0, monthNo: getAdjacentMonth(data[0].monthNo, 'prev')},
      ...data,
      {value: 0, monthNo: getAdjacentMonth(data[5].monthNo, 'next')},
    ].map(item => {
      log(item);
      return {
        ...item,
        monthNo: getGraphMonthValue(item.monthNo),
      };
    });

    log('path data', edgedData);
    const minY = Math.min(...edgedData.map(item => item.value));
    const maxY = Math.max(...edgedData.map(item => item.value));

    const getYaxis = scaleLinear()
      .domain([minY, maxY])
      .range([height - 2, 0]);

    const minX = Math.min(...edgedData?.map(item => item.monthNo));
    const maxX = Math.max(...edgedData?.map(item => item.monthNo));

    const getXaxis = scaleLinear().domain([minX, maxX]).range([0, width]);

    const curvedLine = line()
      .x(d => getXaxis(d.monthNo))
      .y(d => getYaxis(d.value))
      .curve(curveBasis)(edgedData);

    return {
      curve: curvedLine,
    };
  };

  const getGradientAreaSplit = (
    graphLine, // the line created above
    width,
    height, // in my use case this is the graph height / 2, i.e. area above or below y=0
    variant = 'positive',
  ) => {
    // 1) The initial graph line (which can go below or above y=0)
    const gradientAreaSplit =
      Skia.Path.MakeFromSVGString(graphLine ?? '0') ?? Skia.Path.Make();
    const useFirstY =
      (variant === 'positive' && gradientAreaSplit.getPoint(0).y > 0) ||
      (variant === 'negative' && gradientAreaSplit.getPoint(0).y < 0);
    gradientAreaSplit
      // 2) Extend line to y=0 at the right end
      .lineTo(width, height)
      // 3) Extend line to y=0 at the left end
      .lineTo(0, height)
      // 4) Extend line to first point to close the area if that point is in the area, otherwise to y=0 (see useFirstY logic)
      .lineTo(0, useFirstY ? gradientAreaSplit.getPoint(0).y : height);
    return gradientAreaSplit;
  };

  const graphPath = makeGraph();

  const skiaPath = Skia.Path.MakeFromSVGString(graphPath.curve);

  const graphGradientPath = getGradientAreaSplit(
    graphPath.curve,
    width,
    height + 40,
  );

  const [currentIndex, setCurrentIndex] = React.useState(0);
  const [xValue, setXvalue] = React.useState(0);
  const x = useValue(0);
  const y = useComputedValue(
    () => getYForX(skiaPath, x.current),
    [skiaPath, x],
  );

  const getSpentFromX = x => {
    const onePartWidth = width / 8;
    const howManyParts = x / onePartWidth;
    const arrIndex = Math.round(howManyParts - 1.5);
    // log('@@ arrindex: ', arrIndex);
    setCurrentIndex(Math.max(0, Math.min(5, arrIndex)));
  };

  const handleMonthTap = index => {
    console.log('month index: ', index);

    const onePartWidth = width / 8;
    const localX = Math.floor(onePartWidth * (index * 1.02 + 1.5));
    x.current = localX;
    setXvalue(localX);
  };

  React.useEffect(() => {
    getSpentFromX(xValue);
  }, [xValue]);

  const onTouch = useGraphTouchHandler(x, setXvalue);

  return (
    <>
      <View style={styles.headerContainer}>
        <Text style={styles.header}>Month-wise Spends</Text>
        <View
          style={{
            flexDirection: 'row',
            backgroundColor: '#E4F3FF',
            paddingHorizontal: 16,
            paddingVertical: 10,
            borderRadius: 4,
          }}>
          <Text style={styles.header}>
            {months[data[currentIndex].monthNo % 100]} '
            {String(data[currentIndex].monthNo).split('').slice(2, 4).join('')}
          </Text>
          <Text
            style={[
              styles.header,
              {marginLeft: 10, fontFamily: 'Inter-SemiBold'},
            ]}>
            ₹{formatCurrency(data[currentIndex].value)}
          </Text>
        </View>
      </View>
      <View
        style={{transform: [{translateX: -width * 0.04}], paddingBottom: 10}}>
        <Canvas style={{height: height, width}} onTouch={onTouch}>
          <Path
            path={graphGradientPath}
            color="#244566"
            strokeWidth={3}
            style="stroke"
          />
          <Path path={graphGradientPath}>
            <LinearGradient
              start={vec(0, height / 1.2)}
              end={vec(0, 0)}
              colors={['#fff', '#79C0FF85']}
            />
          </Path>
          <Cursor x={x} y={y} />
        </Canvas>
      </View>
      <View style={styles.xAxisContainer}>
        <TouchableOpacity
          style={[styles.arrowButton, {opacity: isPrev ? 1 : 0.2}]}
          disabled={!isPrev}
          onPress={getPrevMonth}>
          <Image
            source={arrow}
            style={[
              styles.arrowImage,
              {transform: [{rotate: '180deg'}, {translateY: -1}]},
            ]}
            resizeMode="contain"
          />
        </TouchableOpacity>
        {data?.map((item, i) => (
          <TouchableOpacity
            key={i}
            style={{padding: 10}}
            onPress={() => handleMonthTap(i)}>
            <Text
              style={[
                styles.monthName,
                {
                  color:
                    item?.monthNo === data[currentIndex].monthNo
                      ? '#204065'
                      : 'rgba(0, 0, 0, 0.60)',
                  fontFamily:
                    item?.monthNo === data[currentIndex].monthNo
                      ? 'Inter-SemiBold'
                      : 'Inter-Regular',
                },
              ]}>
              {months[parseInt(item?.monthNo % 100)]}
            </Text>
          </TouchableOpacity>
        ))}
        <TouchableOpacity
          style={[styles.arrowButton, {opacity: isNext ? 1 : 0.2}]}
          disabled={!isNext}
          onPress={getNextMonth}>
          <Image
            source={arrow}
            style={styles.arrowImage}
            resizeMode="contain"
          />
        </TouchableOpacity>
      </View>
    </>
  );
}

bluntbrain avatar Aug 09 '23 14:08 bluntbrain

@wcandillon @chrfalch Can you guys please check and help in this one, thanks in advance

bluntbrain avatar Aug 09 '23 14:08 bluntbrain

Thank you for reporting this issue, we are really interested to fix it. Could you make the code snippet smaller? Also it is not runnable as a standalone piece of code as it has dependencies with external files. Anything you can do to help us reproduce the issue on our side would be extremely helpful.

wcandillon avatar Aug 10 '23 07:08 wcandillon

Getting the same issue, getting [TypeError: undefined is not a function] in releasew android builds. Seems to be coming from Canvas (Using Skia 1.2.3, RN 0.74.1)

enchorb avatar May 17 '24 23:05 enchorb

closing it but let me know if you have a reproduction

wcandillon avatar May 28 '24 14:05 wcandillon