Skip to content

Conversation

@manoldonev
Copy link
Contributor

@manoldonev manoldonev commented Jul 27, 2018

Switch Android Activity/Fragment/FragmentManager to support library APIs (guideline from Google that this is the way to go as they are deprecating framework fragments from Android P onwards).

Note that migrating to support library FragmentManager implies the rather tedious switch from Animators to Animations API as well (as the supportlib manager only works with Animations). (see comments below)

This should facilitate/enable future Android work with Material Design, shared element transitions, etc.

Fixes #5785
Related to #5789

BREAKING CHANGE:

NativeScript core framework now extends support library APIs versus native framework classes as per Google's latest guidelines:

  • NativeScript activities now extend android.support.v7.app.AppCompatActivity (vs android.app.Activity)
  • NativeScript fragments now extend android.support.v4.app.Fragment (vs android.app.Fragment)
  • NativeScript now works internally with android.support.v4.app.FragmentManager (vs android.app.FragmentManager)

Before:
Custom Android activities extended android.app.Activity

After:
Custom Android activities should extend android.support.v7.app.AppCompatActivity

To migrate the code of your custom activities follow the example below:

Before:

    @JavaProxy("org.myApp.MainActivity")
    class Activity extends android.app.Activity {
        // ...
    }

After:

    @JavaProxy("org.myApp.MainActivity")
    class Activity extends android.support.v7.app.AppCompatActivity {
        // ...
    }

Before:
Default fragment enter animation was Android version specific

After:
Default fragment enter animation is now fade animation for all Android versions. You can customise the transition per navigation entry or globally via the navigation transitions API

Before:
AndroidFragmentCallbacks interface exposed the following onCreateAnimator(...) method

export interface AndroidFragmentCallbacks {
    onCreateAnimator(fragment: any, transit: number, enter: boolean, nextAnim: number, superFunc: Function): any;
    // ...
}

After:
AndroidFragmentCallbacks interface now exposes the following onCreateAnimation(...) method instead (and onCreateAnimator(...) is now removed)

export interface AndroidFragmentCallbacks {
    onCreateAnimation(fragment: any, transit: number, enter: boolean, nextAnim: number, superFunc: Function): any;
    // ...
}

Before:
Transition class exposed the following abstract createAndroidAnimator(...) method

export class Transition {
    public createAndroidAnimator(transitionType: string): any;
    // ...
}

After:
Transition class now exposes the following abstract createAndroidAnimation(...) method instead (and `createAndroidAnimation(...) is now removed)

export class Transition {
    public createAndroidAnimation(transitionType: string): any;
    // ...
}

To migrate the code of your custom transitions follow the example below:

Before:

import * as transition from "tns-core-modules/ui/transition";

export class CustomTransition extends transition.Transition {
    constructor(duration: number, curve: any) {
        super(duration, curve);
    }

    public createAndroidAnimator(transitionType: string): android.animation.Animator {
        var scaleValues = Array.create("float", 2);
        switch (transitionType) {
            case transition.AndroidTransitionType.enter:
            case transition.AndroidTransitionType.popEnter:
                scaleValues[0] = 0;
                scaleValues[1] = 1;
                break;
            case transition.AndroidTransitionType.exit:
            case transition.AndroidTransitionType.popExit:
                scaleValues[0] = 1;
                scaleValues[1] = 0;
                break;
        }
        var objectAnimators = Array.create(android.animation.Animator, 2);
        objectAnimators[0] = android.animation.ObjectAnimator.ofFloat(null, "scaleX", scaleValues);
        objectAnimators[1] = android.animation.ObjectAnimator.ofFloat(null, "scaleY", scaleValues);
        var animatorSet = new android.animation.AnimatorSet();
        animatorSet.playTogether(objectAnimators);

        var duration = this.getDuration();
        if (duration !== undefined) {
            animatorSet.setDuration(duration);
        }
        animatorSet.setInterpolator(this.getCurve());

        return animatorSet;
    }
}

After:

import * as transition from "tns-core-modules/ui/transition";

export class CustomTransition extends transition.Transition {
    constructor(duration: number, curve: any) {
        super(duration, curve);
    }

    public createAndroidAnimation(transitionType: string): android.view.animation.Animation {
        const scaleValues = [];

        switch (transitionType) {
            case transition.AndroidTransitionType.enter:
            case transition.AndroidTransitionType.popEnter:
                scaleValues[0] = 0;
                scaleValues[1] = 1;
                break;
            case transition.AndroidTransitionType.exit:
            case transition.AndroidTransitionType.popExit:
                scaleValues[0] = 1;
                scaleValues[1] = 0;
                break;
        }
            
        const animationSet = new android.view.animation.AnimationSet(false);
        const duration = this.getDuration();
        if (duration !== undefined) {
            animationSet.setDuration(duration);
        }

        animationSet.setInterpolator(this.getCurve());
        animationSet.addAnimation(
            new android.view.animation.ScaleAnimation(
                scaleValues[0], 
                scaleValues[1], 
                scaleValues[0], 
                scaleValues[1]
            ));

        return animationSet;
    }
}

@ghost ghost assigned manoldonev Jul 27, 2018
@ghost ghost added the in progress label Jul 27, 2018
Copy link
Contributor

@vakrilov vakrilov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 👍 👍

There are 2 really minor breaking changes in the public APIs. I don't think they should stop this from going in, but we should note them along with the fact that we are switching to support Activity/Fragments:

frame.d.ts:
AndroidActivityCallbacks.onCreateAnimator -> AndroidActivityCallbacks.onCreateAnimation

transition.d.ts:
Transition.createAndroidAnimator -> Transition.createAndroidAnimation

loadAnimatorMethod = manager.getClass().getDeclaredMethod("loadAnimator", javaClassArray(android.app.Fragment.class, java.lang.Integer.TYPE, java.lang.Boolean.TYPE, java.lang.Integer.TYPE));
if (loadAnimatorMethod != null) {
loadAnimatorMethod.setAccessible(true);
loadAnimationMethod = manager.getClass().getDeclaredMethod("loadAnimation", javaClassArray(android.support.v4.app.Fragment.class, java.lang.Integer.TYPE, java.lang.Boolean.TYPE, java.lang.Integer.TYPE));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its worth debugging to see it the reflection we are using to get the default animations is still valid.
There is a check ( if (loadAnimationMethod != null)) on the next line that will prevent this code form crashing, nut it might be the case that this method does not exist at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I checked it when I added it -- method's good and the default enter animation is now a fast "push fade" (vs fade previously).

import { Transition, AndroidTransitionType } from "./transition";

//http://developer.android.com/training/animation/cardflip.html
// http://developer.android.com/training/animation/cardflip.html
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how much of the code in this tutorial is still relevant as it uses Animators.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left the link intact to keep the traceability as we are using similar logic (more or less) just with Animation APIs.

@manoldonev manoldonev changed the title feat: migrate to android support library apis feat(android): migrate to support library apis Jul 31, 2018
@manoldonev
Copy link
Contributor Author

@vakrilov updated the PR body with the breaking changes section as discussed.

@manoldonev manoldonev merged commit cf034dd into master Jul 31, 2018
@ghost ghost removed the in progress label Jul 31, 2018
@manoldonev manoldonev deleted the mdonev/appcompat-support-api branch July 31, 2018 15:48
@farfromrefug
Copy link
Collaborator

@manoldonev great job! You should mention in my PR that the work is done here.
Thanks again

@manoldonev
Copy link
Contributor Author

@farfromrefug thank you for your effort too!

@farfromrefug
Copy link
Collaborator

@manoldonev I am just glad it triggered the change! Amazing addition to Nativescript

@shiv19
Copy link
Member

shiv19 commented Aug 1, 2018

@manoldonev
I'm on
nativescript@4.3.0-2018-08-01-12054
tns-android@4.3.0-2018-08-01-01
tns-core-modules@4.3.0-2018-07-31-02

In a project that is using NativeScript with JavaScript.
This is the stack trace I got when I built with the above configuration
It is something in the getDefaultAnimation method
stacktrace.txt

@manoldonev
Copy link
Contributor Author

@shiv19 reproduced it in a project with nativescript-plugin-firebase. Working on it -- it is somehow related to the version of the support library that is used (maybe related to
NativeScript/nativescript-cli#3773)

@manoldonev
Copy link
Contributor Author

manoldonev commented Aug 2, 2018

About "Note that migrating to support library FragmentManager implies the rather tedious switch from Animators to Animations API as well (as the supportlib manager only works with Animations)" -- on further research it seems this was true only for older versions of the support library (up to ~26?). Newer versions (27.*) provide dual support for animations OR animators. However, due to some legacy code in NativeScript CLI we are mostly forcing the 26.0.0-alpha version of the support library in NativeScript apps (unless a specific plugin changes that). We will try to clean up the CLI and possibly revert the implementation here to use animators again instead of animations.

manoldonev added a commit that referenced this pull request Aug 2, 2018
Fixes `Error: java.lang.CloneNotSupportedException: Class android.support.v4.app.FragmentManagerImpl$AnimationOrAnimator doesn't implement Cloneable` in specific projects.

Related to #5785
Related to #6129 

BREAKING CHANGE


Before:
Default fragment enter animation was Android version specific

After:
Default fragment enter animation is now fade animation for all Android versions

You can customise the transition per navigation entry or globally via the [navigation transitions API](
https://docs.nativescript.org/core-concepts/navigation#navigation-transitions)
@manoldonev
Copy link
Contributor Author

@shiv19 #6141 is now merged.

manoldonev added a commit to NativeScript/docs-v7 that referenced this pull request Aug 6, 2018
Necessary with NativeScript/NativeScript#6129

NOTE: This is not going to be released with {N} 4.2 (currently only in master branch)
@lock
Copy link

lock bot commented Aug 26, 2019

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked and limited conversation to collaborators Aug 26, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FR] Switch to AppCompatActivity

6 participants