NativeScript/NativeScript

Android: Crash with "markingMode: none" + navigation + backgroundColor change (NS 5.4 / 6.0)

jpierront opened this issue · 27 comments

Environment

  • CLI: 5.4.? & 6.0.2
  • Cross-platform modules: 5.4.2 & 6.0.1
  • Android Runtime: 5.4.0 & 6.0.0
  • NativeScript-Angular: 8.0.0 & 8.0.2
  • Angular: 8.0.0 & 8.0.3

Tested on Android Emulator 8.1

Describe the bug
I get "Attempt to use cleared object reference" errors in a few seconds with the MarkingMode = none. (Again: #7125)

You have to change background colors of elements while navigating to a new route several times. After a while it crash.

Use case: Change background color of current route in a menu.

  • It don’t crash when you only navigating
  • It don’t crash when you only change background color
  • It crash when you do both
  • It don’t crash when you change text color instead of background color
  • It crash quicker when you do lot of calculations in the app

To Reproduce

  • Navigate to a new route every second (router.navigate)
  • Change Background color of current route in the menu ([class.active]=« … »)
  • Overloading the app with other changes (Here other background color changes) (Necessary to accelerate the crash)

Expected behavior
No crash.

Sample project
https://play.nativescript.org/?template=play-ng&id=4RKa1j&v=2
Download it, add "markingMode": "none" in app/package.json under « android » object
Yes, it don’t crash directly in PlayGround.

Additional context
Observed on my application and reproduced in a blank application.
In my app it can crash after 3 or 40 route changes.

I guess this is related #4204 too, and goes way back in time.. We still have no solution for this.

Any news about this issue? I'm getting regular crashes too

I was getting regular crashes aswell, excatly like described in the ticket. Nativescript's Sidekick has template with this issue, if you chose while creating a new prooject a project with sidedrawer, it has this bug in the app.component "[class.selected]="isComponentSelected('/sample')"

Then when navigating between different pages in the sidedrawer, it often pops out that error while app crashes. Sometimes with error reported, soometimes without error.

When this class.selected is removed, app works flawlessly

Yermo commented

May be related to this: #7954

Similar problem here.

I found this in logcat after the crash navigating:

JNI DETECTED ERROR IN APPLICATION: can't call android.graphics.drawable.Drawable android.graphics.drawable.ColorDrawable$ColorState.newDrawable(android.content.res.Resources) on null object

Is disabling markingMode: none the only known workaround at the moment?

Any update on this?

Try this: https://github.com/Yermo/nativescript-mapbox/tree/master/demo-angular#the-long-standing-android-navigation-crash--temporary-workaround-for-tns-core-modules-6112

This works, thanks @Yermo.

I'd like to find a way to patch the module so the patch is maintained after npm install, but I think this will be off topic here.

For me, that works too.
@Yermo Maybe that would be really helpful to send a pull request for your patch.

Yermo commented

@vahidvdn My patch is a complete hack and is not the correct solution. It's just a temporary workaround. The correct solution is to figure out why cachedDrawable is getting collected and nulled out. I've delved into it about as far as is practical. Without some pointers from one of the tns-core-modules developers, I suspect it would take me quite a long time to come up to speed enough to create a correct fix.

A not too ugly solution:

In webpack.config.js (At the beginning of the config.module.rules array), we can add:

platform !== "android" ? {} : {
  test: /tns-core-modules\/ui\/styling\/background\.js$/,
  use: [{
    loader: 'string-replace-loader',
    options: {
      search: 'defaultDrawable = cachedDrawable.newDrawable(nativeView.getResources());',
      replace: 'defaultDrawable = null;',
      strict: true,
    }
  }]
},

Is it fixed in 6.2.0?

Yermo commented

@flocca it is not fixed. I updated to 6.2 just now and ran my regression test and it still slows down and crashes.

In 6.2 the workaround suggested by @jpierront needs to be updated:

platform !== "android" ? {} : {
  test: /@nativescript\/core\/ui\/styling\/background\.android\.js$/,
  use: [{
    loader: 'string-replace-loader',
    options: {
      search: 'defaultDrawable = cachedDrawable.newDrawable(nativeView.getResources());',
      replace: 'defaultDrawable = null;',
      strict: true,
    }
  }]
},

This has saved my life! Thanks for working out the fix, even if it is a temporary hack. One thing though - I can't get the webpack search and replace to work... I've tried moving out of tests and into the main module but no replacement happens and no warning is thrown. Any ideas?

@sandwai-scottjackman npm install --save-dev string-replace-loader ?

Hi @jpierront,

having error when adding the script

ERROR in ../node_modules/tns-core-modules/ui/styling/background.js
Module build failed (from ../node_modules/string-replace-loader/index.js):
Error: Replace failed (strict mode) : defaultDrawable = cachedDrawable.newDrawable(nativeView.getResources()); → defaultDrawable = null;
at replace (/home/ivandejesus/Desktop/ams-pwa-native/node_modules/string-replace-loader/lib/replace.js:17:11)
at Object.processChunk (/home/ivandejesus/Desktop/ams-pwa-native/node_modules/string-replace-loader/lib/processChunk.js:11:17)
@ ../node_modules/@nativescript/core/ui/styling/style-properties.js 6:19-57
@ ../node_modules/@nativescript/core/ui/styling/css-animation-parser.js
@ ../node_modules/@nativescript/core/ui/styling/style-scope.js
@ ../node_modules/tns-core-modules/ui/styling/style-scope.js
@ ../node_modules/nativescript-dev-webpack/load-application-css.js
@ ../node_modules/nativescript-dev-webpack/load-application-css-angular.js
@ ./main.ts

@ivandejesus The replacement is "strict" (required) to be sure that the fix is applied.
Since 6.2 "tns-core-modules" package can be replaced by "@nativescript/core" package.
See #7867 (comment)

j99ht commented

I do not see background.android.js in tns-core-modules@6.3.2
Any idea on how to implement the workaround for this release?

Nativescript 6.3 now started using @nativescript/core so its now: node_modules/@nativescript/core/ui/styling/background.android.js

I am using NativeScript v6.4.1 and Angular v8.3.25 and have found that after removing the [class.-selected]="isComponentSelected('/_some_route_')" lines from RadSideDrawer GridLayout elements, all crashes in my app suddenly went away, including those referencing Drawable. Might be a good place to start in eliminating them from your app as well, until the underlying bugs can be corrected.

For example, in my app.component.tns.html file, I removed the [class.-selected] attribute from all elements, such as to change the following GridLayout element...

<GridLayout columns="auto, *" class="nt-drawer__list-item" (tap)="onNavItemTap('/home')"
        **[class.-selected]="isComponentSelected('/home')"**>
    <Label col="0" text="&#xf015;" class="nt-icon fas"></Label>
    <Label col="1" text="Home" class="p-r-10"></Label>
</GridLayout>

...to the following:

<GridLayout columns="auto, *" class="nt-drawer__list-item" (tap)="onNavItemTap('/home')">
    <Label col="0" text="&#xf015;" class="nt-icon fas"></Label>
    <Label col="1" text="Home" class="p-r-10"></Label>
</GridLayout>

@ebewley,
You are right ! removing the dynamic classes works for me too...
I changed a little bit isComponentSelected and no crashes too ( I hope.. ) :

    import * as application from '@nativescript/core/application';

...
    isComponentSelected (url: string): boolean {
        const sideDrawer = <RadSideDrawer>application.getRootView();
        if (!sideDrawer || !sideDrawer.getIsOpen()) {
            return;
        }
        return this._activatedUrl === url;
    }

@ebewley,
You are right ! removing the dynamic classes works for me too...
I changed a little bit isComponentSelected and no crashes too ( I hope.. ) :

    import * as application from '@nativescript/core/application';

...
    isComponentSelected (url: string): boolean {
        const sideDrawer = <RadSideDrawer>application.getRootView();
        if (!sideDrawer || !sideDrawer.getIsOpen()) {
            return;
        }
        return this._activatedUrl === url;
    }

@arturikoX @ebewley I left the dynamic class and just used rewritten isComponentSelected method and not issues so far (Android Emulator Pixel 9).

Any background color changes in any app may randomly cause a crash. As mentioned above, this hack fixes the problem:

Add this in webpack.config.js just before "return config'

if (platform === 'android') {
  config.module.rules.unshift({
    test: /@nativescript\/core\/ui\/styling\/background\.js$/,
    use: [{
      loader: 'string-replace-loader',
      options: {
        search: 'defaultDrawable = cachedDrawable.newDrawable(nativeView.getResources());',
        replace: 'defaultDrawable = null;',
        strict: true,
      },
    }],
  });
}

Maybe one day the NativeScript team will find a fix ...

@jpierront It seems that this is fixed as mentioned here.

Hey, I don't know if it's related to general discussion. But if it can help anyone, I had similar problem where my app would crush sometimes when using *ngFor for custom navigation bar with [ngClass] or [class.selected] on children (Only with markingMode: none):

<ng-container  *ngFor="let item of topItems">
    <Label  [text]="item" [class.selected]="item === selectedItem" (tap)="selectTopItem(item)"></Label>
</ng-container>

Would give me: "Attempt to use cleared object reference id=..." error

So, I've wrapped the inside ng component and replaced the the [class.selected] with *ngIf directive, which did the work. It's a bit ugly, but now it's not crushes:

<ng-container  *ngFor="let item of topItems">
    <my-component  [item]="item" [selectedItem]="selectedItem" (selectItem)="selectTopItem($event)"></Label>
</ng-container>

// Component template
      <Label *ngIf="item === selectedItem; else notSelected" class="selected top-item" [text]="item" 
               (tap)="selectTopItem.emit(item)"></Label>
        <ng-template #notSelected>
            <Label class=" top-item" [text]="item" (tap)="selectTopItem.emit(item)"></Label>
        </ng-template>

By the way, I couldn't do it outside of the component, because it would mess the order when selected item changes