cannot access Service public class MessagingService extends FirebaseMessagingService - android

I am triying to add push-notification in my ionic mobile application with capacitor but as soon as I install the npm package for push-notification npm install #capacitor/push-notifications i am not longer able to build my app in android studio and i get the error :
../node_modules#capacitor\push-notifications\android\src\main\java\com\capacitorjs\plugins\pushnotifications\MessagingService.java:7: error: cannot access Service
public class MessagingService extends FirebaseMessagingService {
^
class file for android.app.Service not found
This is my package.json file:
{
"name": "push-notifications-app",
"version": "0.0.1",
"author": "Ionic Framework",
"homepage": "https://ionicframework.com/",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"#angular/common": "^14.0.0",
"#angular/core": "^14.0.0",
"#angular/forms": "^14.0.0",
"#angular/platform-browser": "^14.0.0",
"#angular/platform-browser-dynamic": "^14.0.0",
"#angular/router": "^14.0.0",
"#capacitor/android": "4.0.1",
"#capacitor/app": "4.0.1",
"#capacitor/core": "4.0.1",
"#capacitor/haptics": "4.0.1",
"#capacitor/keyboard": "4.0.1",
"#capacitor/push-notifications": "^4.0.1",
"#capacitor/status-bar": "4.0.1",
"#ionic/angular": "^6.1.9",
"rxjs": "~6.6.0",
"tslib": "^2.2.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"#angular-devkit/build-angular": "^14.0.0",
"#angular-eslint/builder": "~13.0.1",
"#angular-eslint/eslint-plugin": "~13.0.1",
"#angular-eslint/eslint-plugin-template": "~13.0.1",
"#angular-eslint/template-parser": "~13.0.1",
"#angular/cli": "^14.0.0",
"#angular/compiler": "^14.0.0",
"#angular/compiler-cli": "^14.0.0",
"#angular/language-service": "^14.0.0",
"#capacitor/cli": "4.0.1",
"#ionic/angular-toolkit": "^6.0.0",
"#types/jasmine": "~3.6.0",
"#types/jasminewd2": "~2.0.3",
"#types/node": "^12.11.1",
"#typescript-eslint/eslint-plugin": "5.3.0",
"#typescript-eslint/parser": "5.3.0",
"eslint": "^7.6.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsdoc": "30.7.6",
"eslint-plugin-prefer-arrow": "1.2.2",
"jasmine-core": "~3.8.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.3.2",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.0.3",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"typescript": "~4.7.3"
},
"description": "An Ionic project"
}
My build.gradle file(android)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.1'
classpath 'com.google.gms:google-services:4.3.5'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
apply from: "variables.gradle"
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
My build.gradle file(android.app)
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "io.ionic.starter"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName '1.0.0-1'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
repositories {
flatDir{
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation project(':capacitor-android')
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
implementation project(':capacitor-cordova-android-plugins')
}
apply from: 'capacitor.build.gradle'
try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}

I had the same problem. I fix it downgrading the capacitor push notifications package version to 1.0.9
npm i #capacitor/push-notifications#1.0.9
npx cap sync android
And including this dependencies to my build.gradle file
implementation platform("com.google.firebase:firebase-bom:30.1.0")
implementation("com.google.firebase:firebase-iid")

i had the same problem too. And resolve it by removing #capacitor-community/fcm and installing #capacitor/push-notifications version 1.0.9 . Also you have to add google-services.json in android/app or android

Related

phonegap-plugin-barcodescanner gives me an error when open it on android studio using ionic 6 an capacitor

Could not find method compile() for arguments [{name=barcodescanner-release-2.1.5, ext=aar}] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
It should open android studio normally to build app but it gives me that error
here is my package.json
`
{
"name": "test-barcode",
"version": "0.0.1",
"author": "Ionic Framework",
"homepage": "https://ionicframework.com/",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"lint": "ng lint"
},
"private": true,
"dependencies": {
"#angular/common": "^14.0.0",
"#angular/core": "^14.0.0",
"#angular/forms": "^14.0.0",
"#angular/platform-browser": "^14.0.0",
"#angular/platform-browser-dynamic": "^14.0.0",
"#angular/router": "^14.0.0",
"#awesome-cordova-plugins/barcode-scanner": "^6.2.0",
"#awesome-cordova-plugins/core": "^6.2.0",
"#capacitor/android": "4.5.0",
"#capacitor/app": "4.1.0",
"#capacitor/core": "4.5.0",
"#capacitor/haptics": "4.0.1",
"#capacitor/keyboard": "4.0.1",
"#capacitor/status-bar": "4.0.1",
"#ionic/angular": "^6.1.9",
"ionicons": "^6.0.3",
"phonegap-plugin-barcodescanner": "^8.1.0",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"#angular-devkit/build-angular": "^14.0.0",
"#angular-eslint/builder": "^14.0.0",
"#angular-eslint/eslint-plugin": "^14.0.0",
"#angular-eslint/eslint-plugin-template": "^14.0.0",
"#angular-eslint/template-parser": "^14.0.0",
"#angular/cli": "^14.0.0",
"#angular/compiler": "^14.0.0",
"#angular/compiler-cli": "^14.0.0",
"#angular/language-service": "^14.0.0",
"#capacitor/cli": "4.5.0",
"#ionic/angular-toolkit": "^6.0.0",
"#types/jasmine": "~4.0.0",
"#types/node": "^12.11.1",
"#typescript-eslint/eslint-plugin": "5.3.0",
"#typescript-eslint/parser": "5.3.0",
"eslint": "^7.6.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsdoc": "30.7.6",
"eslint-plugin-prefer-arrow": "1.2.2",
"jasmine-core": "~4.3.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"ts-node": "~8.3.0",
"typescript": "~4.7.2"
},
"description": "An Ionic project"
}
`
and home-page.ts
`
import { Component } from '#angular/core';
import { BarcodeScanner } from '#awesome-cordova-plugins/barcode-scanner/ngx';
#Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
content: any;
constructor(private barcodeScanner: BarcodeScanner) {}
barcode(){
this.barcodeScanner.scan().then(barcodeData => {
console.log('Barcode data', barcodeData);
}).catch(err => {
console.log('Error', err);
});
}
}
`
and this is build.gradle
`
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "io.ionic.starter"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
repositories {
flatDir{
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation project(':capacitor-android')
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
implementation project(':capacitor-cordova-android-plugins')
}
apply from: 'capacitor.build.gradle'
try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}
`
I think you did everything correctly, I followed your steps and I managed to solve my issue, which was very similar to yours.
The error you are facing should be due to the fact that the plugin uses the old way of including native Android libraries by using compile instead of implementation.
The solution is well explained within this post, where the user Loiic says:
You can change it manually (in Project Structure > Dependencies tab) the
field “configuration” of the the dependency barcodescanner to switch
from “compile” to “implementation”.
Make it a try and see if it works also for you 😉.
Thank you.

PaymentSheet Crashing on Android in v.0.19.0

In stripe-react-native v0.19.0, on Android (13 and 10), presentPaymentSheet call crashes the app without any errors. After installing Sentry I see an
IllegalStateException: ViewTreeLifecycleOwner not found
On iOS everything works perfectly.
To Reproduce
I'm just following this Stripe Developers video about Accept a payment:
export default function TestPaymentScreen(props) {
const [ready, setReady] = useState(false);
const {initPaymentSheet, presentPaymentSheet, loading} = usePaymentSheet();
useEffect(() => {
initializePaymentSheet();
}, []);
const initializePaymentSheet = async () => {
const {paymentIntent, ephemeralKey, customer} =
await fetchPaymentSheetParams();
const {error} = await initPaymentSheet({
customerId: customer,
customerEphemeralKeySecret: ephemeralKey,
paymentIntentClientSecret: paymentIntent,
merchantDisplayName: 'MyApp',
allowsDelayedPaymentMethods: true,
returnURL: 'stripe-example://stripe-redirect',
});
if (error) {
Alert.alert(`Error codes: ${error.code}`, error.message);
} else {
setReady(true);
}
};
const fetchPaymentSheetParams = async () => {
const response = await fetch(`http://192.168.1.67:8000/checkout`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
const {paymentIntent, ephemeralKey, customer} = await response.json();
\
return {
paymentIntent,
ephemeralKey,
customer,
};
};
async function buy() {
const {error} = await presentPaymentSheet();
if (error) {
Alert.alert(`Error code: ${error.code}`, error.message);
} else {
Alert.alert('Success', 'The payment was confirmed successfully');
setReady(false);
}
}
/* ... */
}
When openPaymentSheet() is called pressing the button, the android app crashes without any error in the Metro server console.
The Sentry dashboard shows: IllegalStateException ViewTreeLifecycleOwner not found from androidx.coordinatorlayout.widget.CoordinatorLayout{5876b97 V.E...... ......I. 0,0-0,0 #7f0800cf app:id/coordinator}
Expected behavior
Payment sheet opening
Smartphone
Device: Pixel 4
OS: Android
Version 13
package.json
{
"name": "MyApp",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
"lint": "eslint ."
},
"dependencies": {
"#babel/runtime-corejs3": "^7.13.10",
"#georstat/react-native-image-gallery": "^1.1.0",
"#react-native-async-storage/async-storage": "^1.17.10",
"#react-native-community/checkbox": "^0.5.7",
"#react-native-community/cli-platform-ios": "^5.0.1",
"#react-native-community/datetimepicker": "^3.4.3",
"#react-native-community/geolocation": "^2.0.2",
"#react-native-community/masked-view": "^0.1.10",
"#react-native-community/push-notification-ios": "^1.8.0",
"#react-native-community/slider": "^3.0.3",
"#react-native-community/viewpager": "^4.2.4",
"#react-native-firebase/app": "^11.5.0",
"#react-native-firebase/messaging": "^11.5.0",
"#react-native-google-signin/google-signin": "^6.0.0",
"#react-native/normalize-color": "^2.0.0",
"#react-navigation/drawer": "^5.12.4",
"#sentry/react-native": "^4.2.2",
"#stripe/stripe-react-native": "^0.19.0",
"axios": "^0.27.2",
"core-js": "^3.9.1",
"firebase": "^8.3.1",
"is-base64": "^1.1.0",
"moment": "^2.29.1",
"native-base": "^2.13.15",
"react": "^16.13.1",
"react-dom": "^16.14.0",
"react-is": "^16.13.1",
"react-native": "^0.64.4",
"react-native-animatable": "^1.3.3",
"react-native-app-intro-slider": "^4.0.4",
"react-native-awesome-alerts": "^1.5.2",
"react-native-calendar-picker": "^7.1.1",
"react-native-calendars": "^1.1254.0",
"react-native-check-box": "^2.1.7",
"react-native-city-picker": "^1.0.3",
"react-native-city-select": "^0.1.7",
"react-native-country-picker-modal": "^2.0.0",
"react-native-date-picker": "^4.2.5",
"react-native-device-info": "^8.7.1",
"react-native-dotenv": "^3.3.1",
"react-native-dropdown-picker": "^5.4.2",
"react-native-elements": "^3.3.2",
"react-native-fast-image": "^8.5.11",
"react-native-gesture-handler": "^1.10.3",
"react-native-gifted-chat": "^0.16.3",
"react-native-google-places-autocomplete": "^2.4.1",
"react-native-image-crop-picker": "^0.37.3",
"react-native-image-picker": "^4.10.0",
"react-native-image-slider-box": "^1.0.12",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
"react-native-launch-navigator": "^1.0.8",
"react-native-maps": "git+https://github.com/react-native-community/react-native-maps.git",
"react-native-modal-dropdown": "git+https://github.com/siemiatj/react-native-modal-dropdown.git",
"react-native-open-maps": "^0.4.0",
"react-native-phone-input": "^1.3.2",
"react-native-phone-number-input": "^2.1.0",
"react-native-photo-gallery": "^0.2.4",
"react-native-push-notification": "^8.1.1",
"react-native-really-awesome-button": "^1.6.0",
"react-native-reanimated": "^1.13.4",
"react-native-safe-area-context": "^3.2.0",
"react-native-schedule-availability": "^0.2.0",
"react-native-screens": "^2.18.1",
"react-native-simple-toast": "^1.1.4",
"react-native-star-rating": "^1.1.0",
"react-native-svg": "^12.3.0",
"react-native-svg-transformer": "^0.14.3",
"react-native-svg-uri": "^1.2.3",
"react-native-swipeable-rating": "^0.2.1",
"react-native-swiper": "^1.6.0-nightly.5",
"react-native-switch": "^2.0.0",
"react-native-vector-icons": "^7.1.0",
"react-native-webview": "^11.23.1",
"react-navigation": "^4.4.4",
"react-navigation-drawer": "^2.7.0",
"react-navigation-stack": "^2.10.4",
"react-navigation-tabs": "^2.11.0",
"react-schedule-selector": "^3.0.0",
"rn-schedule-availability": "^0.5.0",
"styled-components": "^5.3.5",
"toggle-switch-react-native": "^3.2.0"
},
"devDependencies": {
"#babel/core": "^7.13.14",
"#babel/runtime": "^7.13.10",
"#react-native-community/eslint-config": "^1.1.0",
"babel-jest": "^25.1.0",
"eslint": "^6.5.1",
"jest": "^25.1.0",
"metro-react-native-babel-preset": "^0.59.0",
"react-test-renderer": "16.13.1"
},
"files": [
".flowconfig",
"android",
"cli.js",
"flow",
"init.sh",
"scripts/compose-source-maps.js",
"scripts/ios-configure-glog.sh",
"scripts/ios-install-third-party.sh",
"scripts/launchPackager.bat",
"scripts/launchPackager.command",
"scripts/node-binary.sh",
"scripts/packager.sh",
"scripts/react-native-xcode.sh",
"jest-preset.js",
"jest",
"lib",
"rn-get-polyfills.js",
"Libraries",
"LICENSE",
"packager",
"react-native.config.js",
"react.gradle",
"React.podspec",
"React-Core.podspec",
"React",
"ReactAndroid",
"ReactCommon",
"README.md",
"third-party-podspecs",
"template",
"local-cli",
"template.config.js",
"!template/node_modules",
"!template/yarn.lock",
"!template/package-lock.json"
],
"jest": {
"preset": "react-native"
}
}
android/app/build.gradle
apply plugin: "com.android.application"
apply plugin: 'com.google.gms.google-services'
import com.android.build.OutputFile
project.ext.react = [
enableHermes: false,
]
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/#sentry/react-native/sentry.gradle"
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
def enableSeparateBuildPerCPUArchitecture = false
def enableProguardInReleaseBuilds = false
def jscFlavor = 'org.webkit:android-jsc:+'
def enableHermes = project.ext.react.get("enableHermes", false);
android {
compileSdkVersion rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
applicationId "com.myapp"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 24
versionName "1.24"
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
applicationVariants.all { variant ->
variant.outputs.each { output ->
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation platform('com.google.firebase:firebase-bom:30.0.1')
implementation 'com.google.firebase:firebase-analytics'
implementation("com.google.firebase:firebase-iid")
implementation(project(':stripe_stripe-react-native')) { exclude module: 'appcompat' }
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
}
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}
apply from: file("../../node_modules/#react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
android/build.gradle
buildscript {
ext {
googlePlayServicesVersion = "+"
firebaseMessagingVersion = "21.1.0"
buildToolsVersion = "29.0.2"
minSdkVersion = 21
compileSdkVersion = 33
targetSdkVersion = 33
}
repositories {
google()
jcenter()
}
dependencies {
classpath("com.android.tools.build:gradle:4.1.0")
classpath "com.google.gms:google-services:4.3.10"
}
}
allprojects {
repositories {
mavenLocal()
maven {
url("$rootDir/../node_modules/react-native/android")
}
maven {
url("$rootDir/../node_modules/jsc-android/dist")
}
google()
jcenter()
maven { url 'https://www.jitpack.io' }
}
}
I found the cause of the crash in my app:
I had added implementation(project(':stripe_stripe-react-native')) { exclude module: 'appcompat' } in the app/build.gradle because without it I get the error Attempt to invoke virtual method'boolean com.facebook.react.uimanager.FabricViewStateManager.hasStateWrappper().
I just tried replacing it with
implementation ("androidx.appcompat:appcompat:1.3.1") {
version {
strictly '1.3.1'
}
}
and everything works fine.

How can I enable Hermes engine in an updated react-native project?

I'm working on a react-native project that was originally using an old react-native version. Updating the project to use the latest react-native version, has been quite succesful in other ways. However, I have not been able to enable Hermes engine for android.
I have followed the official steps here: Using Hermes. To confirm if Hermes is enabled, I clean and rebuild the android project, run the app in the debug mode and use this code:
const isHermes = () => !!global.HermesInternal;
console.log(isHermes());
Also Flipper v0.80.0 cannot find Hermes app.
android/build.gradle
buildscript {
ext {
buildToolsVersion = "29.0.3"
minSdkVersion = 21
compileSdkVersion = 29
targetSdkVersion = 29
ndkVersion = "20.1.5948944"
supportLibVersion = "29.0.0"
googlePlayServicesVersion = "+"
firebaseVersion = "+"
ext.kotlinVersion = '1.3.10'
}
repositories {
google()
jcenter()
}
dependencies {
classpath('com.android.tools.build:gradle:4.1.0')
classpath "com.google.gms:google-services:4.3.3"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
allprojects {
repositories {
mavenLocal()
maven {
url("$rootDir/../node_modules/react-native/android")
}
maven {
url("$rootDir/../node_modules/jsc-android/dist")
}
maven {
url("$rootDir/../node_modules/detox/Detox-android")
}
maven {
url "https://sdks.instabug.com/nexus/repository/instabug-cp"
}
google()
jcenter()
maven { url 'https://www.jitpack.io' }
maven { url 'https://maven.google.com' }
}
}
android/app/build.gradle
apply plugin: "com.android.application"
import com.android.build.OutputFile
project.ext.react = [
enableHermes: true,
]
apply from: "../../node_modules/react-native/react.gradle"
def enableSeparateBuildPerCPUArchitecture = false
def enableProguardInReleaseBuilds = false
def jscFlavor = 'org.webkit:android-jsc:+'
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
def enableHermes = project.ext.react.get("enableHermes", true);
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 8
versionName "2.0.0"
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
vectorDrawables.useSupportLibrary = true
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
release {
if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) {
storeFile file(MYAPP_UPLOAD_STORE_FILE)
storePassword MYAPP_UPLOAD_STORE_PASSWORD
keyAlias MYAPP_UPLOAD_KEY_ALIAS
keyPassword MYAPP_UPLOAD_KEY_PASSWORD
}
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
}
applicationVariants.all { variant ->
variant.outputs.each { output ->
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) {
output.versionCodeOverride =
defaultConfig.versionCode * 1000 + versionCodes.get(abi)
}
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.facebook.react:react-native:+"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation project(':react-native-push-notification')
implementation 'com.google.firebase:firebase-messaging:20.0.0'
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
androidTestImplementation 'junit:junit:4.12'
implementation 'androidx.annotation:annotation:1.1.0'
androidTestImplementation 'androidx.annotation:annotation:1.1.0'
implementation "com.google.firebase:firebase-messaging:{{ android.firebase.messaging }}"
implementation 'me.leolin:ShortcutBadger:1.1.21#aar'
}
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}
apply from: file("../../node_modules/#react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
apply plugin: 'com.google.gms.google-services'
package.json
{
"version": "0.0.1",
"scripts": {
"start": "react-native start",
"test": "jest",
"lint": "eslint ."
},
"dependencies": {
"#aws-sdk/client-cognito-identity": "^3.4.0",
"#aws-sdk/client-s3": "^3.4.0",
"#aws-sdk/credential-provider-cognito-identity": "^3.4.0",
"#react-native-community/async-storage": "^1.6.1",
"#react-native-community/datetimepicker": "^3.0.9",
"#react-native-community/masked-view": "^0.1.10",
"#react-native-community/netinfo": "^3.2.1",
"#react-native-community/push-notification-ios": "^1.8.0",
"#react-native-firebase/analytics": "^11.0.0",
"#react-native-firebase/app": "^11.0.0",
"#react-native-firebase/messaging": "^11.0.0",
"#react-navigation/bottom-tabs": "^5.11.2",
"#react-navigation/drawer": "^5.11.4",
"#react-navigation/native": "^5.8.10",
"#react-navigation/stack": "^5.12.8",
"acorn": "^6.1.1",
"acorn-jsx": "^5.0.1",
"add": "^2.0.6",
"compare-versions": "^3.6.0",
"instabug-reactnative": "^10.0.0",
"jwt-decode": "^2.2.0",
"lodash": "^4.17.20",
"moment": "^2.29.1",
"moment-timezone": "^0.5.33",
"react": "17.0.1",
"react-native": "0.64.0",
"react-native-add-calendar-event": "^4.0.0",
"react-native-animatable": "^1.3.3",
"react-native-app-intro-slider": "^4.0.4",
"react-native-auth0": "^2.7.0",
"react-native-aws3": "0.0.9",
"react-native-elements": "^3.1.0",
"react-native-gesture-handler": "^1.9.0",
"react-native-google-places-autocomplete": "^1.3.9",
"react-native-hyperlink": "^0.0.19",
"react-native-image-crop-picker": "^0.35.3",
"react-native-image-zoom-viewer": "^3.0.1",
"react-native-in-app-notification": "^3.1.0",
"react-native-keyboard-aware-scroll-view": "^0.9.2",
"react-native-keychain": "^6.2.0",
"react-native-localize": "^2.0.2",
"react-native-modal": "^11.4.0",
"react-native-popup-menu": "^0.15.10",
"react-native-push-notification": "^3.1.9",
"react-native-reanimated": "^1.13.2",
"react-native-safe-area-context": "^3.1.9",
"react-native-screens": "^2.16.1",
"react-native-share": "^5.0.0",
"react-native-tab-view": "^2.10.0",
"react-native-touchable-scale": "^2.1.2",
"react-native-vector-icons": "^7.1.0",
"react-native-windows": "^0.63.15",
"react-redux": "^7.1.0",
"redux": "^4.0.4",
"redux-logger": "^3.0.6",
"redux-persist": "^5.10.0",
"redux-thunk": "^2.3.0",
"rn-fetch-blob": "^0.12.0",
"validator": "^13.5.2",
"yarn": "^1.22.10"
},
"devDependencies": {
"#babel/core": "^7.12.49",
"#babel/runtime": "^7.12.5",
"#react-native-community/eslint-config": "^2.0.0",
"#types/react-native": "^0.63.43",
"#typescript-eslint/eslint-plugin": "^4.11.1",
"#typescript-eslint/parser": "^4.11.1",
"babel-eslint": "^10.0.1",
"babel-jest": "^26.6.3",
"eslint": "^7.14.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-react": "^7.22.0",
"jest": "^26.6.3",
"jetifier": "^1.6.4",
"metro-react-native-babel-preset": "^0.64.0",
"prettier": "^2.2.1",
"prop-types": "^15.7.2",
"react-test-renderer": "17.0.1",
"typescript": "^4.1.3"
},
"resolutions": {
"graceful-fs": "4.2.4"
},
"jest": {
"preset": "react-native"
}
}
proguard-rules.pro
-keep class com.facebook.hermes.unicode.** { *; }
-keep class com.facebook.jni.** { *; }

React Native - TypeError: Cannot read property 'clean' of undefined

React Native v 0.62.2.
npm run android gives me below error:
TypeError: Cannot read property 'clean' of undefined
ExceptionsManager.js:76 Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication)
package.json
{
"name": "client",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"postinstall": "rn-nodeify --install process,buffer,crypto,stream,assert,events,vm --hack",
"graphql:codegen": "apollo client:codegen --target typescript"
},
"dependencies": {
"#react-native-community/art": "^1.2.0",
"#react-native-community/async-storage": "^1.10.3",
"#react-native-community/cameraroll": "^1.7.1",
"#react-native-community/clipboard": "^1.2.2",
"#react-native-community/geolocation": "^2.0.2",
"#react-native-community/masked-view": "^0.1.10",
"#react-native-community/push-notification-ios": "^1.2.0",
"#skele/components": "^1.0.0-alpha.40",
"apollo-cache-inmemory": "^1.6.6",
"apollo-client": "^2.6.10",
"apollo-link-context": "^1.0.20",
"apollo-link-error": "^1.1.13",
"apollo-link-http": "^1.5.17",
"apollo-link-ws": "^1.0.20",
"apollo-upload-client": "^13.0.0",
"assert": "^1.5.0",
"base-64": "^0.1.0",
"buffer": "^4.9.2",
"ethereumjs-abi": "^0.6.8",
"ethereumjs-util": "^6.2.0",
"ethereumjs-wallet": "^0.6.3",
"events": "^1.1.1",
"firebase": "^7.15.1",
"graphql": "^14.6.0",
"graphql-tag": "^2.10.3",
"i18n-js": "^3.5.1",
"invert-color": "^2.0.0",
"lodash.memoize": "^4.1.2",
"moment": "^2.26.0",
"process": "^0.11.10",
"react": "^16.13.1",
"react-apollo": "^3.1.5",
"react-dom": "^16.11.1",
"react-native": "^0.62.2",
"react-native-actionsheet": "^2.4.2",
"react-native-app-intro-slider": "^4.0.2",
"react-native-button": "^3.0.1",
"react-native-camera": "^3.28.0",
"react-native-crypto": "^2.2.0",
"react-native-dark-mode": "^0.2.2",
"react-native-device-info": "^5.5.7",
"react-native-dialog-input": "^1.0.7",
"react-native-elements": "^2.0.0",
"react-native-fast-image": "^8.1.5",
"react-native-firebase": "^5.6.0",
"react-native-geocoding": "^0.4.0",
"react-native-gesture-handler": "^1.6.1",
"react-native-google-places-autocomplete": "^1.7.1",
"react-native-image-crop-picker": "^0.25.3",
"react-native-image-filter-kit": "^0.7.1",
"react-native-image-picker": "^2.3.1",
"react-native-image-progress": "^1.1.1",
"react-native-image-view": "^2.1.9",
"react-native-indicators": "^0.17.0",
"react-native-ionicons": "^4.6.5",
"react-native-keyboard-aware-scroll-view": "^0.9.1",
"react-native-keyboard-aware-view": "^0.0.14",
"react-native-localize": "^1.4.0",
"react-native-maps": "^0.27.1",
"react-native-modal": "^11.5.6",
"react-native-modalbox": "^2.0.0",
"react-native-progress": "^4.1.2",
"react-native-push-notification": "^3.5.2",
"react-native-randombytes": "^3.5.3",
"react-native-reanimated": "~1.9.0",
"react-native-safe-area-context": "^3.0.2",
"react-native-safe-area-view": "^1.1.1",
"react-native-screens": "^2.8.0",
"react-native-search-box": "^0.0.19",
"react-native-splash-screen": "^3.2.0",
"react-native-swiper": "^1.6.0",
"react-native-vector-icons": "^6.6.0",
"react-native-video": "^4.4.5",
"react-native-view-more-text": "^2.1.0",
"react-native-web": "~0.12.2",
"react-navigation": "^4.1.0",
"react-navigation-drawer": "^2.4.11",
"react-navigation-hooks": "^1.1.0",
"react-navigation-stack": "^2.1.0",
"react-navigation-tabs": "^2.7.0",
"react-redux": "^7.2.0",
"readable-stream": "^1.0.33",
"redux": "^4.0.5",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0",
"remote-redux-devtools": "^0.5.16",
"rn-nodeify": "^10.2.0",
"stream-browserify": "^1.0.0",
"subscriptions-transport-ws": "^0.9.16",
"uuidv4": "^6.0.7",
"vm-browserify": "0.0.4"
},
"devDependencies": {
"#babel/core": "^7.6.2",
"#babel/runtime": "^7.6.2",
"#react-native-community/eslint-config": "^0.0.5",
"#types/apollo-upload-client": "^8.1.3",
"#types/jest": "^24.0.24",
"#types/react-native": "^0.60.25",
"#types/react-native-push-notification": "^3.0.9",
"#types/react-test-renderer": "16.9.1",
"#typescript-eslint/eslint-plugin": "^2.12.0",
"#typescript-eslint/parser": "^2.12.0",
"apollo": "^2.27.4",
"babel-jest": "^25.1.0",
"babel-plugin-transform-remove-console": "^6.9.4",
"eslint": "^6.5.1",
"jest": "^25.1.0",
"jetifier": "^1.6.6",
"metro": "^0.59.0",
"metro-react-native-babel-preset": "^0.56.0",
"react-test-renderer": "16.9.0",
"typescript": "^3.8.3"
},
"jest": {
"preset": "react-native",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
]
},
"react-native": {
"_stream_transform": "readable-stream/transform",
"_stream_readable": "readable-stream/readable",
"_stream_writable": "readable-stream/writable",
"_stream_duplex": "readable-stream/duplex",
"_stream_passthrough": "readable-stream/passthrough",
"stream": "stream-browserify",
"crypto": "react-native-crypto",
"vm": "vm-browserify"
},
"browser": {
"_stream_transform": "readable-stream/transform",
"_stream_readable": "readable-stream/readable",
"_stream_writable": "readable-stream/writable",
"_stream_duplex": "readable-stream/duplex",
"_stream_passthrough": "readable-stream/passthrough",
"stream": "stream-browserify",
"crypto": "react-native-crypto",
"vm": "vm-browserify"
},
"resolutions": {
"#react-native-community/cli-debugger-ui": "4.7.0"
}
}
android/bundle.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
buildToolsVersion = "28.0.3"
minSdkVersion = 16
compileSdkVersion = 28
targetSdkVersion = 28
supportLibVersion = "28.0.0"
supportVersion = "28.0.0"
googlePlayServicesVersion = "15.0.1"
facebookSdkVersion = "4.37.0"
}
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:3.5.2")
classpath 'com.google.gms:google-services:4.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url("$rootDir/../node_modules/react-native/android")
}
maven {
// Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist")
}
maven { url 'https://maven.google.com' }
maven { url "https://www.jitpack.io" }
google()
jcenter()
}
}
android/app/bundle.gradle
apply plugin: "com.android.application"
import com.android.build.OutputFile
project.ext.react = [
entryFile: "index.js",
enableHermes: false, // clean and rebuild if changing
]
apply from: "../../node_modules/react-native/react.gradle"
def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
def jscFlavor = 'org.webkit:android-jsc:+'
def enableHermes = project.ext.react.get("enableHermes", false);
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
dexOptions {
javaMaxHeapSize "3g"
}
defaultConfig {
applicationId "com.client"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.12"
missingDimensionStrategy 'react-native-camera', 'general'
multiDexEnabled true
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
release {
if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) {
storeFile file(MYAPP_UPLOAD_STORE_FILE)
storePassword MYAPP_UPLOAD_STORE_PASSWORD
keyAlias MYAPP_UPLOAD_KEY_ALIAS
keyPassword MYAPP_UPLOAD_KEY_PASSWORD
}
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://facebook.github.io/react-native/docs/signed-apk-android.
signingConfig signingConfigs.release
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
packagingOptions {
pickFirst "lib/armeabi-v7a/libc++_shared.so"
pickFirst "lib/arm64-v8a/libc++_shared.so"
pickFirst "lib/x86/libc++_shared.so"
pickFirst "lib/x86_64/libc++_shared.so"
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.facebook.react:react-native:+" // From node_modules
implementation 'androidx.appcompat:appcompat:1.2.0-rc01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'
implementation 'com.android.support:multidex:1.0.3'
implementation 'androidx.multidex:multidex:2.0.1'
implementation "com.google.android.gms:play-services-base:17.0.0"
implementation "com.google.firebase:firebase-core:17.0.1"
implementation "com.google.firebase:firebase-auth:17.0.0"
implementation "com.google.firebase:firebase-firestore:19.0.0"
implementation "com.google.firebase:firebase-messaging:19.0.0"
implementation "com.google.firebase:firebase-storage:17.0.0"
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}
apply from: file("../../node_modules/#react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
apply plugin: 'com.google.gms.google-services'
As #AkilaDevinda suggested I looked for clean in code and found that I am using cleanExtractedImagesCache() function from library
react-native-image-filter-kit.
Issue was that I had disabled auto-link for android in react-native.config.js
module.exports = {
dependencies: {
'react-native-image-filter-kit': {
platforms: {
android: null, // disable Android platform, other platforms will still autolink if provided
},
},
}
Hence, in android cleanExtractedImagesCache() wasn't found and caused undefined error. Thanks.

nativescript - problem with firebase plugin

I create a fresh nativescript project according to this article by running these commands:
tns create fitApp --appid "..."
cd fitApp
tns plugin add nativescript-plugin-firebase
tns run android
and changed AndroidManifest.xml accordingly and put google-services.json as it said.but i get this error:
Could not find support-compat.jar (com.android.support:support-compat:28.0.0).
Searched in the following locations:
https://maven.google.com/com/android/support/support-compat/28.0.0/support-compat-28.0.0.jar
this is my package.json:
{
"nativescript": {
"id": "...",
"tns-android": {
"version": "5.2.1"
},
"tns-ios": {
"version": "5.2.0"
}
},
"description": "NativeScript Application",
"license": "SEE LICENSE IN <your-license-filename>",
"repository": "<fill-your-repository-here>",
"dependencies": {
"#angular/animations": "~7.2.0",
"#angular/common": "~7.2.0",
"#angular/compiler": "~7.2.0",
"#angular/core": "~7.2.0",
"#angular/forms": "~7.2.0",
"#angular/http": "~7.2.0",
"#angular/platform-browser": "~7.2.0",
"#angular/platform-browser-dynamic": "~7.2.0",
"#angular/router": "~7.2.0",
"nativescript-angular": "~7.2.1",
"nativescript-plugin-firebase": "^8.1.1",
"nativescript-theme-core": "~1.0.4",
"reflect-metadata": "~0.1.12",
"rxjs": "~6.3.0",
"tns-core-modules": "~5.2.0",
"zone.js": "~0.8.26"
},
"devDependencies": {
"#angular/compiler-cli": "~7.2.0",
"#nativescript/schematics": "~0.5.0",
"#ngtools/webpack": "~7.2.0",
"nativescript-dev-typescript": "~0.8.0",
"nativescript-dev-webpack": "~0.20.0"
},
"gitHead": "f548ec926e75201ab1b7c4a3a7ceefe7a4db15af",
"readme": "NativeScript Application"
}
build.gradle:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.1'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

Categories

Resources