Skip to content

Commit cf034dd

Browse files
authored
feat(android): migrate to support library apis (#6129)
Switch Activity / Fragment / FragmentManager implementation from native framework to support library APIs 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) The implications of these changes should be mostly transparent to the developer except for the fact that the support library Fragment / FragmentManager work with Animation APIs versus Animator APIs. For Android API Levels lower than 28 the new Fragment API uses a different fragment enter animation by default. You can customise the transition per navigation entry or globally via the [navigation transitions API](https://docs.nativescript.org/core-concepts/navigation#navigation-transitions) Before: Default fragment enter animation was fade animation After: Default fragment enter animation for API levels lower than 28 is now a fast "push fade" animation; default fragment enter animation for API levels equal to or greater than 28 remains fade animation Before: AndroidFragmentCallbacks interface exposed the following `onCreateAnimator(...)` method ``` ts 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) ``` ts export interface AndroidFragmentCallbacks { onCreateAnimation(fragment: any, transit: number, enter: boolean, nextAnim: number, superFunc: Function): any; // ... } ``` Before: Transition class exposed the following abstract `createAndroidAnimator(...)` method ``` ts export class Transition { public createAndroidAnimator(transitionType: string): any; // ... } ``` After: Transition class now exposes the following abstract `createAndroidAnimation(...)` method instead (and `createAndroidAnimation(...) is now removed) ``` ts export class Transition { public createAndroidAnimation(transitionType: string): any; // ... } ``` To migrate the code of your custom transitions follow the example below: Before: ``` ts 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: ``` ts 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; } } ```
1 parent 5db3e67 commit cf034dd

21 files changed

+341
-325
lines changed

tests/app/navigation/custom-transition.android.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ export class CustomTransition extends transition.Transition {
55
super(duration, curve);
66
}
77

8-
public createAndroidAnimator(transitionType: string): android.animation.Animator {
9-
var scaleValues = Array.create("float", 2);
8+
public createAndroidAnimation(transitionType: string): android.view.animation.Animation {
9+
const scaleValues = [];
10+
1011
switch (transitionType) {
1112
case transition.AndroidTransitionType.enter:
1213
case transition.AndroidTransitionType.popEnter:
@@ -19,18 +20,22 @@ export class CustomTransition extends transition.Transition {
1920
scaleValues[1] = 0;
2021
break;
2122
}
22-
var objectAnimators = Array.create(android.animation.Animator, 2);
23-
objectAnimators[0] = android.animation.ObjectAnimator.ofFloat(null, "scaleX", scaleValues);
24-
objectAnimators[1] = android.animation.ObjectAnimator.ofFloat(null, "scaleY", scaleValues);
25-
var animatorSet = new android.animation.AnimatorSet();
26-
animatorSet.playTogether(objectAnimators);
27-
28-
var duration = this.getDuration();
23+
24+
const animationSet = new android.view.animation.AnimationSet(false);
25+
const duration = this.getDuration();
2926
if (duration !== undefined) {
30-
animatorSet.setDuration(duration);
27+
animationSet.setDuration(duration);
3128
}
32-
animatorSet.setInterpolator(this.getCurve());
3329

34-
return animatorSet;
30+
animationSet.setInterpolator(this.getCurve());
31+
animationSet.addAnimation(
32+
new android.view.animation.ScaleAnimation(
33+
scaleValues[0],
34+
scaleValues[1],
35+
scaleValues[0],
36+
scaleValues[1]
37+
));
38+
39+
return animationSet;
3540
}
3641
}

tns-core-modules/application/application.android.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ export class AndroidApplication extends Observable implements AndroidApplication
4242
public paused: boolean;
4343
public nativeApp: android.app.Application;
4444
public context: android.content.Context;
45-
public foregroundActivity: android.app.Activity;
46-
public startActivity: android.app.Activity;
45+
public foregroundActivity: android.support.v7.app.AppCompatActivity;
46+
public startActivity: android.support.v7.app.AppCompatActivity;
4747
public packageName: string;
4848
// we are using these property to store the callbacks to avoid early GC collection which would trigger MarkReachableObjects
4949
private callbacks: any = {};
@@ -221,7 +221,7 @@ global.__onLiveSync = function () {
221221
};
222222

223223
function initLifecycleCallbacks() {
224-
const setThemeOnLaunch = profile("setThemeOnLaunch", (activity: android.app.Activity) => {
224+
const setThemeOnLaunch = profile("setThemeOnLaunch", (activity: android.support.v7.app.AppCompatActivity) => {
225225
// Set app theme after launch screen was used during startup
226226
const activityInfo = activity.getPackageManager().getActivityInfo(activity.getComponentName(), android.content.pm.PackageManager.GET_META_DATA);
227227
if (activityInfo.metaData) {
@@ -232,11 +232,11 @@ function initLifecycleCallbacks() {
232232
}
233233
});
234234

235-
const notifyActivityCreated = profile("notifyActivityCreated", function (activity: android.app.Activity, savedInstanceState: android.os.Bundle) {
235+
const notifyActivityCreated = profile("notifyActivityCreated", function (activity: android.support.v7.app.AppCompatActivity, savedInstanceState: android.os.Bundle) {
236236
androidApp.notify(<AndroidActivityBundleEventData>{ eventName: ActivityCreated, object: androidApp, activity, bundle: savedInstanceState });
237237
});
238238

239-
const subscribeForGlobalLayout = profile("subscribeForGlobalLayout", function (activity: android.app.Activity) {
239+
const subscribeForGlobalLayout = profile("subscribeForGlobalLayout", function (activity: android.support.v7.app.AppCompatActivity) {
240240
const rootView = activity.getWindow().getDecorView().getRootView();
241241
// store the listener not to trigger GC collection before collecting the method
242242
this.onGlobalLayoutListener = new android.view.ViewTreeObserver.OnGlobalLayoutListener({
@@ -250,7 +250,7 @@ function initLifecycleCallbacks() {
250250
});
251251

252252
const lifecycleCallbacks = new android.app.Application.ActivityLifecycleCallbacks({
253-
onActivityCreated: profile("onActivityCreated", function (activity: android.app.Activity, savedInstanceState: android.os.Bundle) {
253+
onActivityCreated: profile("onActivityCreated", function (activity: android.support.v7.app.AppCompatActivity, savedInstanceState: android.os.Bundle) {
254254
setThemeOnLaunch(activity);
255255

256256
if (!androidApp.startActivity) {
@@ -264,7 +264,7 @@ function initLifecycleCallbacks() {
264264
}
265265
}),
266266

267-
onActivityDestroyed: profile("onActivityDestroyed", function (activity: android.app.Activity) {
267+
onActivityDestroyed: profile("onActivityDestroyed", function (activity: android.support.v7.app.AppCompatActivity) {
268268
if (activity === androidApp.foregroundActivity) {
269269
androidApp.foregroundActivity = undefined;
270270
}
@@ -278,7 +278,7 @@ function initLifecycleCallbacks() {
278278
gc();
279279
}),
280280

281-
onActivityPaused: profile("onActivityPaused", function (activity: android.app.Activity) {
281+
onActivityPaused: profile("onActivityPaused", function (activity: android.support.v7.app.AppCompatActivity) {
282282
if ((<any>activity).isNativeScriptActivity) {
283283
androidApp.paused = true;
284284
notify(<ApplicationEventData>{ eventName: suspendEvent, object: androidApp, android: activity });
@@ -287,7 +287,7 @@ function initLifecycleCallbacks() {
287287
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityPaused, object: androidApp, activity: activity });
288288
}),
289289

290-
onActivityResumed: profile("onActivityResumed", function (activity: android.app.Activity) {
290+
onActivityResumed: profile("onActivityResumed", function (activity: android.support.v7.app.AppCompatActivity) {
291291
androidApp.foregroundActivity = activity;
292292

293293
if ((<any>activity).isNativeScriptActivity) {
@@ -298,15 +298,15 @@ function initLifecycleCallbacks() {
298298
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityResumed, object: androidApp, activity: activity });
299299
}),
300300

301-
onActivitySaveInstanceState: profile("onActivityResumed", function (activity: android.app.Activity, outState: android.os.Bundle) {
301+
onActivitySaveInstanceState: profile("onActivityResumed", function (activity: android.support.v7.app.AppCompatActivity, outState: android.os.Bundle) {
302302
androidApp.notify(<AndroidActivityBundleEventData>{ eventName: SaveActivityState, object: androidApp, activity: activity, bundle: outState });
303303
}),
304304

305-
onActivityStarted: profile("onActivityStarted", function (activity: android.app.Activity) {
305+
onActivityStarted: profile("onActivityStarted", function (activity: android.support.v7.app.AppCompatActivity) {
306306
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityStarted, object: androidApp, activity: activity });
307307
}),
308308

309-
onActivityStopped: profile("onActivityStopped", function (activity: android.app.Activity) {
309+
onActivityStopped: profile("onActivityStopped", function (activity: android.support.v7.app.AppCompatActivity) {
310310
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityStopped, object: androidApp, activity: activity });
311311
})
312312
});

tns-core-modules/application/application.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ export interface AndroidActivityEventData {
288288
/**
289289
* The activity.
290290
*/
291-
activity: any /* android.app.Activity */;
291+
activity: any /* android.support.v7.app.AppCompatActivity */;
292292

293293
/**
294294
* The name of the event.
@@ -378,7 +378,7 @@ export class AndroidApplication extends Observable {
378378
/**
379379
* The currently active (loaded) [android Activity](http://developer.android.com/reference/android/app/Activity.html). This property is automatically updated upon Activity events.
380380
*/
381-
foregroundActivity: any /* android.app.Activity */;
381+
foregroundActivity: any /* android.support.v7.app.AppCompatActivity */;
382382

383383
/**
384384
* Deprecated. Please use startActivity, foregroundActivity or context property.
@@ -388,7 +388,7 @@ export class AndroidApplication extends Observable {
388388
/**
389389
* The main (start) Activity for the application.
390390
*/
391-
startActivity: any /* android.app.Activity */;
391+
startActivity: any /* android.support.v7.app.AppCompatActivity */;
392392

393393
/**
394394
* The name of the application package.

tns-core-modules/ui/core/view/view.android.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ interface TouchListener {
4747
}
4848

4949
interface DialogFragment {
50-
new(): android.app.DialogFragment;
50+
new(): android.support.v4.app.DialogFragment;
5151
}
5252

5353
function initializeDisabledListener(): void {
@@ -120,7 +120,7 @@ function initializeDialogFragment() {
120120
}
121121
}
122122

123-
class DialogFragmentImpl extends android.app.DialogFragment {
123+
class DialogFragmentImpl extends android.support.v4.app.DialogFragment {
124124
public owner: View;
125125
private _fullscreen: boolean;
126126
private _stretched: boolean;
@@ -141,7 +141,7 @@ function initializeDialogFragment() {
141141
this._dismissCallback = options.dismissCallback;
142142
this._shownCallback = options.shownCallback;
143143
this.owner._dialogFragment = this;
144-
this.setStyle(android.app.DialogFragment.STYLE_NO_TITLE, 0);
144+
this.setStyle(android.support.v4.app.DialogFragment.STYLE_NO_TITLE, 0);
145145

146146
const dialog = new DialogImpl(this, this.getActivity(), this.getTheme());
147147

@@ -225,13 +225,13 @@ function getModalOptions(domId: number): DialogOptions {
225225
export class View extends ViewCommon {
226226
public static androidBackPressedEvent = androidBackPressedEvent;
227227

228-
public _dialogFragment: android.app.DialogFragment;
228+
public _dialogFragment: android.support.v4.app.DialogFragment;
229229
private _isClickable: boolean;
230230
private touchListenerIsSet: boolean;
231231
private touchListener: android.view.View.OnTouchListener;
232232
private layoutChangeListenerIsSet: boolean;
233233
private layoutChangeListener: android.view.View.OnLayoutChangeListener;
234-
private _manager: android.app.FragmentManager;
234+
private _manager: android.support.v4.app.FragmentManager;
235235

236236
nativeViewProtected: android.view.View;
237237

@@ -263,7 +263,7 @@ export class View extends ViewCommon {
263263
}
264264
}
265265

266-
public _getFragmentManager(): android.app.FragmentManager {
266+
public _getFragmentManager(): android.support.v4.app.FragmentManager {
267267
let manager = this._manager;
268268
if (!manager) {
269269
let view: View = this;
@@ -280,7 +280,7 @@ export class View extends ViewCommon {
280280
}
281281

282282
if (!manager && this._context) {
283-
manager = (<android.app.Activity>this._context).getFragmentManager();
283+
manager = (<android.support.v7.app.AppCompatActivity>this._context).getSupportFragmentManager();
284284
}
285285

286286
this._manager = manager;

tns-core-modules/ui/core/view/view.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,7 @@ export abstract class View extends ViewBase {
659659
/**
660660
* @private
661661
*/
662-
_getFragmentManager(): any; /* android.app.FragmentManager */
662+
_getFragmentManager(): any; /* android.support.v4.app.FragmentManager */
663663

664664
/**
665665
* Updates styleScope or create new styleScope.

tns-core-modules/ui/editable-text-base/editable-text-base.android.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ function dismissSoftInput(owner: EditableTextBase): void {
3636
if (!dismissKeyboardTimeoutId) {
3737
dismissKeyboardTimeoutId = setTimeout(() => {
3838
const owner = dismissKeyboardOwner && dismissKeyboardOwner.get();
39-
const activity = (owner && owner._context) as android.app.Activity;
39+
const activity = (owner && owner._context) as android.support.v7.app.AppCompatActivity;
4040
const nativeView = owner && owner.nativeViewProtected;
4141
dismissKeyboardTimeoutId = null;
4242
dismissKeyboardOwner = null;

tns-core-modules/ui/frame/activity.android.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ if ((<any>global).__snapshot || (<any>global).__snapshotEnabled) {
88

99
//@ts-ignore
1010
@JavaProxy("com.tns.NativeScriptActivity")
11-
class NativeScriptActivity extends android.app.Activity {
11+
class NativeScriptActivity extends android.support.v7.app.AppCompatActivity {
1212
private _callbacks: AndroidActivityCallbacks;
1313
public isNativeScriptActivity;
1414
constructor() {
@@ -54,7 +54,7 @@ class NativeScriptActivity extends android.app.Activity {
5454
this._callbacks.onBackPressed(this, super.onBackPressed);
5555
}
5656

57-
public onRequestPermissionsResult(requestCode: number, permissions: Array<String>, grantResults: Array<number>): void {
57+
public onRequestPermissionsResult(requestCode: number, permissions: Array<string>, grantResults: Array<number>): void {
5858
this._callbacks.onRequestPermissionsResult(this, requestCode, permissions, grantResults, undefined /*TODO: Enable if needed*/);
5959
}
6060

tns-core-modules/ui/frame/fragment.android.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { AndroidFragmentCallbacks, setFragmentCallbacks, setFragmentClass } from "./frame";
22

33
@JavaProxy("com.tns.FragmentClass")
4-
class FragmentClass extends android.app.Fragment {
4+
class FragmentClass extends android.support.v4.app.Fragment {
55
// This field is updated in the frame module upon `new` (although hacky this eases the Fragment->callbacks association a lot)
66
private _callbacks: AndroidFragmentCallbacks;
77

@@ -14,9 +14,8 @@ class FragmentClass extends android.app.Fragment {
1414
this._callbacks.onHiddenChanged(this, hidden, super.onHiddenChanged);
1515
}
1616

17-
public onCreateAnimator(transit: number, enter: boolean, nextAnim: number): android.animation.Animator {
18-
let result = this._callbacks.onCreateAnimator(this, transit, enter, nextAnim, super.onCreateAnimator);
19-
return result;
17+
public onCreateAnimation(transit: number, enter: boolean, nextAnim: number): android.view.animation.Animation {
18+
return this._callbacks.onCreateAnimation(this, transit, enter, nextAnim, super.onCreateAnimation);
2019
}
2120

2221
public onStop(): void {

0 commit comments

Comments
 (0)