{"record":{"id":"2ea16c2b2067d182","repo":"didi/DoKit","slug":"transformation-transformation-key-crashe","errorCode":null,"errorMessage":"Transformation \" + transformation.key() + \" crashed with exception.","messagePattern":"Transformation \" \\+ transformation\\.key\\(\\) \\+ \" crashed with exception\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/BitmapHunter.java","lineNumber":433,"sourceCode":"      RequestHandler requestHandler = requestHandlers.get(i);\n      if (requestHandler.canHandleRequest(request)) {\n        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);\n      }\n    }\n\n    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);\n  }\n\n  static Bitmap applyCustomTransformations(List<Transformation> transformations, Bitmap result) {\n    for (int i = 0, count = transformations.size(); i < count; i++) {\n      final Transformation transformation = transformations.get(i);\n      Bitmap newResult;\n      try {\n        newResult = transformation.transform(result);\n      } catch (final RuntimeException e) {\n        DokitPicasso.HANDLER.post(new Runnable() {\n          @Override public void run() {\n            throw new RuntimeException(\n                \"Transformation \" + transformation.key() + \" crashed with exception.\", e);\n          }\n        });\n        return null;\n      }\n\n      if (newResult == null) {\n        final StringBuilder builder = new StringBuilder() //\n            .append(\"Transformation \")\n            .append(transformation.key())\n            .append(\" returned null after \")\n            .append(i)\n            .append(\" previous transformation(s).\\n\\nTransformation list:\\n\");\n        for (Transformation t : transformations) {\n          builder.append(t.key()).append('\\n');\n        }\n        DokitPicasso.HANDLER.post(new Runnable() {\n          @Override public void run() {","sourceCodeStart":415,"sourceCodeEnd":451,"githubUrl":"https://github.com/didi/DoKit/blob/626827cddb2feb2f3aee87a52a064b4e5ca2bed4/Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/BitmapHunter.java#L415-L451","documentation":"applyCustomTransformations catches a RuntimeException thrown by a Transformation.transform(bitmap) and rethrows it (with the transformation's key in the message) on the main thread via DokitPicasso.HANDLER.post, returning null so the request fails. The original exception is attached as the cause. The message identifies exactly which transformation in the chain crashed — the real diagnosis is in the cause.","triggerScenarios":"A custom Transformation (set via .transform(t)) throwing on OOM, on recycled-input bitmaps, on ARGB_8888 vs RGB_565 config assumptions, or on zero-sized bitmaps when targetWidth/targetHeight were never resolved (e.g. into(notification) without resize).","commonSituations":"Rounded-corner or blur transformations on huge bitmaps throwing OutOfMemoryError-adjusive IllegalArgumentException; transform that calls Bitmap.createScaledBitmap with width/height of 0 because the request had no target size; transformation run on a bitmap already recycled by a previous transformation.","solutions":["Read the cause stack trace in logcat — it names the line inside your Transformation that threw","Guard size math: skip scaling when src.getWidth()/getHeight() <= 0 or target dims are 0","Pair the transform with .resize(w, h) / .fit() so dimensions are known before transforming","Recycle intermediate bitmaps you create and never recycle the input bitmap (that is error 86)","For memory-bound transforms, force a smaller decode first: .config(Bitmap.Config.RGB_565) or lower resize"],"exampleFix":"// before\nclass CropSquare implements Transformation {\n  @Override public Bitmap transform(Bitmap src) {\n    int size = Math.min(src.getWidth(), src.getHeight());\n    return Bitmap.createBitmap(src, (src.getWidth()-size)/2, (src.getHeight()-size)/2, size, size);\n  }\n  @Override public String key() { return \"square()\"; }\n}\n\n// after\n@Override public Bitmap transform(Bitmap src) {\n  if (src.isRecycled() || src.getWidth() == 0 || src.getHeight() == 0) return src;\n  int size = Math.min(src.getWidth(), src.getHeight());\n  Bitmap out = Bitmap.createBitmap(src, (src.getWidth()-size)/2, (src.getHeight()-size)/2, size, size);\n  if (out != src) src.recycle();\n  return out;\n}","handlingStrategy":"try-catch","validationCode":"// Harden the transformation itself rather than validating at the call site:\nstatic Bitmap safeTransform(Transformation t, Bitmap src) {\n  if (src == null || src.isRecycled() || src.getWidth() == 0 || src.getHeight() == 0) return src;\n  try { return t.transform(src); } catch (RuntimeException e) { return src; }\n}","typeGuard":null,"tryCatchPattern":"// Picasso rethrows on the MAIN thread via HANDLER.post, so a caller try-catch around\n// .into() will NOT catch it. Set a default Thread.UncaughtExceptionHandler during debug,\nor// make transformations exception-free (see validationCode). The cause chain in logcat\n// names the failing transformation — fix it there.","preventionTips":["Never let transform() throw: validate dimensions and recycled state first","Pair .transform() with .resize()/.fit() so target dimensions are known","Keep transformations small and memory-light; pre-scale via .resize() instead of inside transform()"],"tags":["android","picasso","bitmap","transformation"],"backgroundTag":null,"analyzedSha":"626827cddb2feb2f3aee87a52a064b4e5ca2bed4","analyzedAt":"2026-08-14T12:45:58.758Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}