Skip to content
This repository has been archived by the owner on Jul 13, 2019. It is now read-only.

nativescript v2.4 + android support v25.1.0 ClassNotFoundError #16

Closed
SBD580 opened this issue Dec 17, 2016 · 37 comments
Closed

nativescript v2.4 + android support v25.1.0 ClassNotFoundError #16

SBD580 opened this issue Dec 17, 2016 · 37 comments

Comments

@SBD580
Copy link

SBD580 commented Dec 17, 2016

Hi,

Just try to upgrade the application to latest nativescript, seems like the plugin break the static binding generation. I'm not sure if this is an issue of the plugin or of the nativescript runtime.

I'm getting this exception:

:asbg:generateBindings                 
java.lang.IllegalArgumentException: clazz is null
        at org.nativescript.staticbindinggenerator.ClassInfo.<init>(ClassInfo.java:25)
        at org.nativescript.staticbindinggenerator.ClassInfo.<init>(ClassInfo.java:20)
        at org.nativescript.staticbindinggenerator.ClassInfo.<init>(ClassInfo.java:20)
        at org.nativescript.staticbindinggenerator.ClassInfo.getSuperclass(ClassInfo.java:132)
        at com.tns.bindings.Dump.isApplicationClass(Dump.java:626)
        at com.tns.bindings.Dump.getSupportedMethods(Dump.java:323)
        at com.tns.bindings.Dump.generateProxy(Dump.java:211)
        at com.tns.bindings.ProxyGenerator.generateProxy(ProxyGenerator.java:36)
        at org.nativescript.staticbindinggenerator.Generator.writeBindings(Generator.java:87)
        at org.nativescript.staticbindinggenerator.Main.main(Main.java:15)

After hours of trying to figure out what the problem is I tried to remove the plugin and the build did well.

I have found a workaround, changing "android.support" to "com.android.support" (either in this plugin sourcecode, or at the bindings.txt of the ASBG).

Shay

@SBD580
Copy link
Author

SBD580 commented Dec 17, 2016

A small after-prepare hook (as a better workaround) if anyone is interested;

var path = require("path");
var fs = require("fs");

module.exports = function() {
    console.log("nativescript-snackbar-fix");

    var snackbarFilePath = path.join(__dirname, "..", "..", "platforms", "android", "src","main","assets","app","tns_modules","nativescript-snackbar","snackbar.js");
    if (fs.existsSync(snackbarFilePath)) {
        var snackbarFileContent = fs.readFileSync(snackbarFilePath).toString();

        if (snackbarFileContent.indexOf('com.android.support') != -1) {
            return;
        }

        snackbarFileContent = snackbarFileContent.replace(/android\.support/g,'com.android.support');

        fs.writeFileSync(snackbarFilePath, snackbarFileContent);
    }
};

@SBD580
Copy link
Author

SBD580 commented Dec 19, 2016

Changing this plugin code is not solving the problem (runtime errors) the solution I used was to fix the line at the bindings.txt file (android.support => com.android.support)

Probably not an issue with this plugin rather with ASBG. Can close this one

@bradmartin
Copy link
Owner

You have an issue for sure but I'm fairly certain it's nothing with the plugin. I just created a fresh app from tns create testapp --tsc, added android, added the plugin, setup the snackbar code, build, then run and everything works fine. You might want to report this issue with the bindings on the Nativescript repo and see if they have any idea. Sorry I have nothing more for you, I can't reproduce though.

@bradmartin
Copy link
Owner

snackbartest
tns info from the app root.

@SBD580
Copy link
Author

SBD580 commented Dec 19, 2016

Hi,

Thanks for checking this out. What version of android support library did you used?

@bradmartin
Copy link
Owner

I have everything up to date in the sdk manager.

@bradmartin
Copy link
Owner

bradmartin commented Dec 19, 2016

well dang, nevermind there is an update available, on a different laptop 😄 . Let me try one more time and see.

@bradmartin
Copy link
Owner

Alright I did encounter an error after just updating the libs and building. Not the exact error you had but an error when running. To fix I did the following:

tns create testSnack --tsc
tns platform add android
tns plugin add nativescript-snackbar
tns build android - to ensure everything was good
tns livesync android --watch - the app starts all is good

  • added the code for the snackbar saved changes and the snackbar works fine 👍

Something must be messed up in the project you have, I would try cleaning the platform remove/add and then do a complete build. Uninstall the old .apk from the device and then run the new build and see if that cleans it up.

androidvs

@SBD580
Copy link
Author

SBD580 commented Dec 19, 2016

Already tried all that =/ (including the plugging remove/add)

The thing is fixing the file manually does solve the problem, from the other hand no other dependency is needed the 'com.' prefix so this indeed strange...

I will try on a template project like you did and will see what the different.

Thanks again!

@SBD580
Copy link
Author

SBD580 commented Dec 25, 2016

Well, after the exact project was build successfully on an other computer I tries several thing - the thing that got that fixed was removing SDK platform 7.1.1 (API 25).

@bradmartin did you had this installed when trying to reproduce?

@bradmartin
Copy link
Owner

If that's the latest available then yes I did.

@roblav96
Copy link
Contributor

roblav96 commented Dec 27, 2016

@Pip3r4o Ref: NativeScript/android#665

@SBD580 @bradmartin I can confirm this plugin is causing this error. It's been driving me nuts for the past 3 days 🤕 lol
So tonight I decided to make a blank project (tns create wtf --tsc) and individually add plugins until this error came up and the conclusion is that sadly this plugin does produce this error. I'm looking into why this error is happening.

To rule out project discrepancies, every time I added a plugin I ran this npm script (npm run ra) to guarantee a fresh project each try:

"scripts": {
	"init": "npm install && tns install",
	"a": "tns livesync android --watch",
	"ra": "sudo rm -rf hooks && sudo rm -rf node_modules && sudo rm -rf platforms && sleep 1 && npm run init && npm run au && npm run a",
	"au": "adb uninstall org.nativescript.wtf"
},

tns info:

┌──────────────────┬───────────────────────┬────────────────┬───────────────┐
│ Component        │ Current version       │ Latest version │ Information   │
│ nativescript     │ 2.5.0-2016-12-23-7505 │ 2.4.2          │ Up to date    │
│ tns-core-modules │ 2.4.4                 │ 2.4.4          │ Up to date    │
│ tns-android      │ 2.4.1                 │ 2.4.1          │ Up to date    │
│ tns-ios          │                       │ 2.4.0          │ Not installed │
└──────────────────┴───────────────────────┴────────────────┴───────────────┘

@bradmartin
Copy link
Owner

Good info Rob. I hit the error but then got it working as I mentioned so it was hit or miss for me. Nonetheless hopefully Pete knows something.

@roblav96
Copy link
Contributor

[UPDATE]

What I've tried so far:

  • gradle compile compile "com.android.support:design:+"
  • gradle compile compile "com.android.support:design:24.2.1"
  • gradle compile compile "com.android.support:design:22.2.0"
  • commenting out entire snackbar.android.js file

So far commenting the snackbar.android.js file is the only thing that stops the error from occurring so my guess is that it must be something to do with the binding generator? I'm not too sure :X but we're getting close :D

@petekanev
Copy link

@bradmartin comment back in a couple of hours to remind me this is an issue :D

@bradmartin
Copy link
Owner

@Pip3r4o ping

@roblav96
Copy link
Contributor

@Pip3r4o bump 🍔

@petekanev
Copy link

petekanev commented Dec 29, 2016 via email

@roblav96
Copy link
Contributor

@Pip3r4o Get off github right meow...! go enjoy vacation 🏖 👙 🍺 🍻 🌞

@roblav96
Copy link
Contributor

roblav96 commented Jan 4, 2017

@Pip3r4o any word on this by any chance?

@petekanev
Copy link

@roblav96 @bradmartin I ran a couple of tests today and these are my findings:

  • building and running with compileSdk 22 produces no build error, but a dex is not generated for the custom Snackbar class, and the app crashes runtime when I try to access the Snackbar.
  • building and running with compileSdk 23/24/25 produces no build error, a dex is generated, and the Snackbar works without issues.

I ran the tests on Geny emulators API 23 and API 19, as well as on a device API 19

And this is what my Android SDK manager looks like: I try to have the latest build tools, support libraries and platforms installed.
androidsdk

My guess is that the design library v. 22.x.x's Snackbar.Callback refers to some internals that aren't present in any of the other support libraries of similar or close version.

I'd suggest you try hardcoding the dependency to design to 23 or greater, and testing it out that way. Brad, you could also check if the project property supportVersion is 23 or higher and outputting a warning message or something.

@echap
Copy link

echap commented Jan 10, 2017

@bradmartin I am facing the same issue, ( I firstly thought it was a tns core issue ), then I went through the exact same debugging pipe as @roblav96 , which led me to nativescript-snackbar. I'm trying to find a correct fix, I will write it down as sool as I can. A real fix would be very welcome.

@petekanev
Copy link

@echap can you say what the latest installed android SDK versions you have installed and which versions your project uses (normally becomes evident from the build log)

@echap
Copy link

echap commented Jan 10, 2017

@Pip3r4o here's my sdk manager : (I'm using SDK Platform API 25)

sdk versions

@petekanev
Copy link

petekanev commented Jan 10, 2017

@bradmartin @echap @roblav96 I inspected the jars used in a project compiled with --compileSdk 22 flag which use the 22.2.1 version of the support libraries, and the Snackbar.Callback class that is extended in the plugin is not present, meaning it was added at a later point - 23.

As I suggested earlier - you can hardcode the version to 23+ or check if a lower one is used and output a warning message that the plugin won't work properly (at all) on android.

As for the original error reported in the post, I am unable to reproduce it, the static binding generator, when debugged, does not output clazz is null, but skips the Snackbar.Callback altogether, because it cannot be found as being part of the android classes. In the event of clazz is null crash during bindings generation the process exits and all pending classes that have yet to be generated (like the NativeScript Activity) are omitted, hence the runtime error.

I'd like to ask either of you experiencing the build-time exception to please zip the build/intermediates/exploded-aar dir as well as attach the build-tools\android-static-binding-generator\bindings.txt file and send them over so I can inspect them and figure out why the generator's process is terminated. #16 (comment) Thanks!

@echap
Copy link

echap commented Jan 10, 2017

@Pip3r4o @bradmartin @roblav96 You propose to hardcore version 23 in platforms/android/app.gradle

android {
  productFlavors {
    "nativescript-snackbar" {
      dimension "nativescript-snackbar"
    }
  }
}

//optional elements
dependencies {
	 def supportVer = "23.0.0+";

      if(project.hasProperty("supportVersion")) {
       supportVer = supportVersion
      }

     compile "com.android.support:design:$supportVer"
}

This fix seems to work for me

Edit : It dos not work that much 👎

@petekanev
Copy link

petekanev commented Jan 10, 2017

@echap any output or crash log?

Edit: Good news, I got the build-time crash!

@bradmartin
Copy link
Owner

@Pip3r4o just let me know what you find out and a good fix for the plugin going forward to avoid issues 👍

@echap
Copy link

echap commented Jan 10, 2017

@Pip3r4o Still the same issue as I initially reported here.

@echap
Copy link

echap commented Jan 10, 2017

Since I did not have any efficient fix, I downgraded my sdk for the moment.

@roblav96
Copy link
Contributor

roblav96 commented Jan 10, 2017

While this hasn't been building properly, I whipped up a snackbar for Android and iOS if you want it.

// 

import * as application from 'application'
import { EventData } from 'data/observable'
import { Color } from 'color'
import { Page } from 'ui/page'
import { topmost } from 'ui/frame'
import { View } from 'ui/core/view'
import { StackLayout } from 'ui/layouts/stack-layout'
import { AbsoluteLayout } from 'ui/layouts/absolute-layout'
import { GestureTypes, GestureEventData } from 'ui/gestures'
import { action, ActionOptions } from 'ui/dialogs'
import { DockLayout } from 'ui/layouts/dock-layout'
import { GridLayout } from 'ui/layouts/grid-layout'
import { LayoutBase } from 'ui/layouts/layout-base'
import { device, screen, isIOS, isAndroid } from 'platform'
import { isNullOrUndefined } from 'utils/types'
import { Animation, AnimationDefinition } from 'ui/animation'
import { AnimationCurve } from 'ui/enums'
import { Button } from 'ui/button'
import { Label } from 'ui/label'



export interface SnackBarOptions {
	text: string
	timeout?: number
	button?: string
	align?: 'top' | 'bottom'
	textColor?: Color
	backgroundColor?: Color
	buttonColor?: Color
	buttonBackgroundColor?: Color
}

export class SnackBar {

	private _opts: SnackBarOptions
	get opts() {
		return this._opts
	}
	set opts(opts) {
		this._opts = opts
	}

	private _resolve
	get resolve() {
		return this._resolve
	}
	set resolve(resolve) {
		this._resolve = resolve
	}

	private _reject
	get reject() {
		return this._reject
	}
	set reject(reject) {
		this._reject = reject
	}

	private _timeout: number

	build(opts: SnackBarOptions): Promise<boolean> {
		let page = topmost().currentPage
		if (!page) {
			return new Promise(function(resolve, reject) {
				setTimeout(function() {
					resolve(opts)
				}, 1000)
			}).then(() => this.build(opts))
		}

		let theOpts = {
			text: 'SnackBar',
			timeout: 3000,
			align: 'bottom',
			textColor: new Color('white'),
			backgroundColor: new Color('#323232'),
			buttonColor: new Color('white'),
			buttonBackgroundColor: new Color('#585858'),
		} as SnackBarOptions
		Object.keys(opts).forEach(function(key) {
			theOpts[key] = opts[key]
		})
		this.opts = theOpts

		let layout = new DockLayout()
		layout.backgroundColor = this.opts.backgroundColor
		layout.verticalAlignment = this.opts.align
		layout.opacity = 0
		layout.padding = '15 30'

		let button: Button
		if (this.opts.button) {
			let wrapper = new StackLayout()
			wrapper.verticalAlignment = 'center'
			button = new Button()
			button.text = this.opts.button
			button.color = this.opts.buttonColor
			button.backgroundColor = this.opts.buttonBackgroundColor
			if (application.ios) {
				button.style.padding = '5 10'
			}
			wrapper.addChild(button)
			DockLayout.setDock(wrapper, 'right')
			layout.addChild(wrapper)
		}

		let label = new Label()
		label.text = this.opts.text
		label.color = this.opts.textColor
		label.verticalAlignment = 'center'
		label.textWrap = true
		label.fontSize = 16
		label.marginRight = 10
		layout.addChild(label)

		let t = 1
		if (application.android) {
			page._addView(layout, (<any>page)._childrenCount)

			if (button) {
				button.on(<any>GestureTypes.tap, (args: EventData) => {
					button.off(GestureTypes.tap)
					clearTimeout(this._timeout)
					this.destroy(layout, true)
				})
			}

			let aparent: android.view.ViewGroup = layout.parent.android
			let root: android.view.ViewGroup = <any>aparent.getRootView()
			let delta = 0
			let which = (this.opts.align == 'top') ? 'statusBarBackground' : 'navigationBarBackground'
			// console.log('screen.mainScreen.heightPixels', screen.mainScreen.heightPixels)
			let i: number, len: number = root.getChildCount()
			for (i = 0; i < len; i++) {
				let child = root.getChildAt(i)
				// console.log('child.toString()', child.toString())
				// console.log('child.getHeight()', child.getHeight())
				if (child.toString().indexOf(which) != -1) {
					delta = child.getHeight()
					break
				}
			}
			delta = SnackBar.getMeasure(delta)
			if (this.opts.align == 'top') {
				layout.marginTop = delta
			} else {
				layout.marginBottom = delta
			}

			let alayout: android.view.ViewGroup = layout.android
			alayout.setClickable(true)
			aparent.removeView(alayout)

			if (SnackBar._keyboard != 0) {
				// console.log('delta', delta)
				let params = alayout.getLayoutParams() as org.nativescript.widgets.CommonLayoutParams
				// console.log('SnackBar._keyboard', SnackBar._keyboard)
				// console.log('params.bottomMargin', params.bottomMargin)
				params.bottomMargin = params.bottomMargin + SnackBar._keyboard + 120 // + 144 // + 72 // + (delta * screen.mainScreen.scale)
				// console.log('params.bottomMargin', params.bottomMargin)
				alayout.setLayoutParams(params)
			}

			root.addView(alayout, root.getChildCount() - 2)
		}

		if (application.ios) {
			t = 50
			let base: LayoutBase = <any>page.layoutView
			if (this.opts.align == 'top') {
				let uiutils = require('ui/utils')
				layout.paddingTop = layout.paddingTop + uiutils.ios.getStatusBarHeight()
			}
			base.addChild(layout)

			setTimeout(() => {
				let iview: UIView = layout.ios
				base.removeChild(layout)
				let rootCtrl: UINavigationController = application.ios.rootController
				rootCtrl.view.addSubview(iview)
				if (this.opts.align == 'bottom') {
					let y = screen.mainScreen.heightDIPs - iview.frame.size.height - SnackBar._keyboard
					iview.frame = CGRectMake(iview.frame.origin.x, y, iview.frame.size.width, iview.frame.size.height)
				}

				if (button) {
					button.onLoaded()
					button.on(<any>GestureTypes.tap, (args: EventData) => {
						button.off(GestureTypes.tap)
						clearTimeout(this._timeout)
						this.destroy(layout, true)
					})
				}

			}, 25)
		}

		setTimeout(() => {
			let height = SnackBar.getMeasure(layout.getMeasuredHeight()) / 2
			layout.translateY = (this.opts.align == 'top') ? -height : height
			layout.animate({
				opacity: 1,
				translate: { x: 0, y: 0 },
				curve: AnimationCurve.easeOut,
				duration: SnackBar.getAnimDuration(150),
			})
		}, t)

		this._timeout = setTimeout(() => {
			if (button) {
				button.off(<any>GestureTypes.touch)
			}
			this.destroy(layout)
		}, this.opts.timeout)

		return new Promise((resolve, reject) => {
			this._resolve = resolve
			this._reject = reject
		})

	}

	private destroy(layout: DockLayout, action?: boolean) {
		let height = SnackBar.getMeasure(layout.getMeasuredHeight()) / 2
		let y = (this.opts.align == 'top') ? -height : height
		layout.animate({
			opacity: 0,
			translate: { x: 0, y },
			curve: AnimationCurve.easeOut,
			duration: SnackBar.getAnimDuration(150),
		}).then(() => {
			if (application.android) {
				let aview: android.view.ViewGroup = layout.android
				let root: android.view.ViewGroup = <any>aview.getRootView()
				root.removeView(aview)
			}
			if (application.ios) {
				let iview: UIView = layout.ios
				iview.removeFromSuperview()
			}
			this.resolve(action)
		}).catch((error) => {
			this.reject(error)
		})
	}



	private static getMeasure(dim: number): number {
		return Math.round((application.android) ? dim / screen.mainScreen.scale : dim)
	}

	private static getAnimDuration(t: number): number {
		return (application.android) ? t * 1.5 : t
	}



	private static _keyboard: number = 0
	private static _t: number = null
	public static _initKeyboard() {
		let top = topmost()
		if (
			(isAndroid == true && (!top || !top.android || !top.android.rootViewGroup))
			||
			(isIOS == true && !application.ios)
		) {
			clearTimeout(this._t)
			this._t = setTimeout(() => SnackBar._initKeyboard(), 100)
			return
		}
		clearTimeout(this._t)

		if (application.android) {
			let group = top.android.rootViewGroup as android.view.ViewGroup
			group.getViewTreeObserver().addOnGlobalLayoutListener(new android.view.ViewTreeObserver.OnGlobalLayoutListener({
				onGlobalLayout: function() {
					let pageHeight = topmost().currentPage.getMeasuredHeight()
					if (!isFinite(this.maxHeight)) {
						this.maxHeight = pageHeight
					}
					let shown = pageHeight != this.maxHeight
					if (this.showing != shown) {
						this.showing = shown
						SnackBar._keyboard = this.maxHeight - pageHeight
					}
				}
			}))
		}

		if (application.ios) {
			application.ios.addNotificationObserver(UIKeyboardWillChangeFrameNotification, function(notification: NSNotification) {
				let rect: CGRect = notification.userInfo.valueForKey(UIKeyboardFrameEndUserInfoKey).CGRectValue
				SnackBar._keyboard = (screen.mainScreen.heightDIPs == rect.origin.y) ? 0 : rect.size.height
			})
		}

	}

}

application.on(application.launchEvent, () => SnackBar._initKeyboard())

Also respects keyboard size and position.

Usage:

return new SnackBar().build({
	text: '😎 Wanna rate us?',
	button: 'YES!',
	timeout: 5000,
}).then(function(result) {
	if (result == true) {
		return buildRatePrompt()
	}
})

@petekanev
Copy link

petekanev commented Jan 10, 2017

@bradmartin @roblav96 now that I managed to reliably reproduce the crash, I found the cause - a little bug in the static-binding-generator related to java inner class name strings. Pushing the fix in master shortly. Snackbars should still be used with design library 23+ in their current state.

Thanks for all your help in dealing with this bug!

@bradmartin
Copy link
Owner

@roblav96 that's very nice, I might use that for the iOS version to get rid of the cocoapod dependency. If you have time to make a PR for it that's cool too, if not no worries 👍

@roblav96
Copy link
Contributor

@Pip3r4o No problem! <3 tns

@bradmartin Ye for sure!

@echap
Copy link

echap commented Jan 17, 2017

@roblav96 any update on this ? :)

@petekanev
Copy link

@echap use the latest android runtime (android@next) to get the fix.

@eduardoturconi
Copy link

I'm having the same error at :asbg:generateBindings using

  • nativescript: 2.6.0-2017-01-17-7788

  • tns-android: 2.6.0-next-2017-01-20-1637

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants