firebase Crashlytics

This commit is contained in:
kratikpal 2024-08-12 10:12:27 +05:30
parent a41f500aee
commit cd24aff9f3
11 changed files with 135 additions and 71 deletions

View File

@ -2,6 +2,7 @@ plugins {
id "com.android.application" id "com.android.application"
// START: FlutterFire Configuration // START: FlutterFire Configuration
id 'com.google.gms.google-services' id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
// END: FlutterFire Configuration // END: FlutterFire Configuration
id "kotlin-android" id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.

View File

@ -21,6 +21,7 @@ plugins {
id "com.android.application" version "7.3.0" apply false id "com.android.application" version "7.3.0" apply false
// START: FlutterFire Configuration // START: FlutterFire Configuration
id "com.google.gms.google-services" version "4.3.15" apply false id "com.google.gms.google-services" version "4.3.15" apply false
id "com.google.firebase.crashlytics" version "2.8.1" apply false
// END: FlutterFire Configuration // END: FlutterFire Configuration
id "org.jetbrains.kotlin.android" version "1.9.10" apply false id "org.jetbrains.kotlin.android" version "1.9.10" apply false
} }

View File

@ -4,6 +4,7 @@ import 'package:cheminova/provider/collect_kyc_provider.dart';
import 'package:cheminova/provider/user_provider.dart'; import 'package:cheminova/provider/user_provider.dart';
import 'package:cheminova/screens/splash_screen.dart'; import 'package:cheminova/screens/splash_screen.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -28,6 +29,14 @@ Future<void> main() async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
FlutterError.onError = (errorDetails) {
FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
};
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
if (!kIsWeb) { if (!kIsWeb) {
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
@ -46,7 +55,9 @@ Future<void> main() async {
NotificationDetails( NotificationDetails(
android: AndroidNotificationDetails(channel.id, channel.name, android: AndroidNotificationDetails(channel.id, channel.name,
icon: '@mipmap/ic_launcher', importance: Importance.max), icon: '@mipmap/ic_launcher', importance: Importance.max),
iOS: const DarwinNotificationDetails())); iOS: const DarwinNotificationDetails(),
),
);
const AndroidInitializationSettings initializationSettingsAndroid = const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher'); AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings darwinInitializationSettings = const DarwinInitializationSettings darwinInitializationSettings =

View File

@ -9,17 +9,17 @@ class NotificationListResponse {
if (json['notifications'] != null) { if (json['notifications'] != null) {
notifications = <Notifications>[]; notifications = <Notifications>[];
json['notifications'].forEach((v) { json['notifications'].forEach((v) {
notifications!.add(new Notifications.fromJson(v)); notifications!.add(Notifications.fromJson(v));
}); });
} }
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>(); final Map<String, dynamic> data = <String, dynamic>{};
data['return_message'] = this.returnMessage; data['return_message'] = returnMessage;
if (this.notifications != null) { if (notifications != null) {
data['notifications'] = data['notifications'] =
this.notifications!.map((v) => v.toJson()).toList(); notifications!.map((v) => v.toJson()).toList();
} }
return data; return data;
} }
@ -54,14 +54,14 @@ class Notifications {
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>(); final Map<String, dynamic> data = <String, dynamic>{};
data['_id'] = this.sId; data['_id'] = sId;
data['title'] = this.title; data['title'] = title;
data['msg'] = this.msg; data['msg'] = msg;
data['added_for'] = this.addedFor; data['added_for'] = addedFor;
data['createdAt'] = this.createdAt; data['createdAt'] = createdAt;
data['updatedAt'] = this.updatedAt; data['updatedAt'] = updatedAt;
data['__v'] = this.iV; data['__v'] = iV;
return data; return data;
} }
} }

View File

@ -3,7 +3,6 @@ import 'dart:io';
import 'package:cheminova/models/get_pd_response.dart'; import 'package:cheminova/models/get_pd_response.dart';
import 'package:cheminova/screens/data_submit_successfull.dart'; import 'package:cheminova/screens/data_submit_successfull.dart';
import 'package:cheminova/services/api_client.dart'; import 'package:cheminova/services/api_client.dart';
import 'package:cheminova/services/secure__storage_service.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
@ -217,10 +216,6 @@ class CollectKycProvider extends ChangeNotifier {
} }
Future<void> submitCollectKycForm(BuildContext context) async { Future<void> submitCollectKycForm(BuildContext context) async {
final dio = Dio();
final token = await SecureStorageService().read(key: 'access_token');
final headers = {'Authorization': 'Bearer $token'};
// Construct the FormData // Construct the FormData
final data = FormData.fromMap({ final data = FormData.fromMap({
'name': nameController.text.trim(), 'name': nameController.text.trim(),
@ -236,22 +231,28 @@ class CollectKycProvider extends ChangeNotifier {
'aadhar_number': aadharNumberController.text.trim(), 'aadhar_number': aadharNumberController.text.trim(),
'gst_number': gstNumberController.text.trim(), 'gst_number': gstNumberController.text.trim(),
if (panCard != null) if (panCard != null)
'pan_img': await MultipartFile.fromFile(panCard!.path, filename: 'pan_card.jpg'), 'pan_img': await MultipartFile.fromFile(panCard!.path,
filename: 'pan_card.jpg'),
if (aadharCard != null) if (aadharCard != null)
'aadhar_img': await MultipartFile.fromFile(aadharCard!.path, filename: 'aadhar_card.jpg'), 'aadhar_img': await MultipartFile.fromFile(aadharCard!.path,
filename: 'aadhar_card.jpg'),
if (gstRegistration != null) if (gstRegistration != null)
'gst_img': await MultipartFile.fromFile(gstRegistration!.path, filename: 'gst_registration.jpg'), 'gst_img': await MultipartFile.fromFile(gstRegistration!.path,
filename: 'gst_registration.jpg'),
if (pesticideLicense != null) if (pesticideLicense != null)
'pesticide_license_img': await MultipartFile.fromFile(pesticideLicense!.path, filename: 'pesticide_license.jpg'), 'pesticide_license_img': await MultipartFile.fromFile(
pesticideLicense!.path,
filename: 'pesticide_license.jpg'),
if (selfieEntranceBoard != null) if (selfieEntranceBoard != null)
'selfie_entrance_img': await MultipartFile.fromFile(selfieEntranceBoard!.path, filename: 'selfie_entrance_board.jpg'), 'selfie_entrance_img': await MultipartFile.fromFile(
selfieEntranceBoard!.path,
filename: 'selfie_entrance_board.jpg'),
}); });
setLoading(true); setLoading(true);
try { try {
Response response =
Response response = await _apiClient.post(ApiUrls.createCollectKycUrl, await _apiClient.post(ApiUrls.createCollectKycUrl, data: data);
data: data);
setLoading(false); setLoading(false);

View File

@ -12,7 +12,6 @@ import 'package:cheminova/screens/product_sales_data.dart';
import 'package:cheminova/screens/update_inventory_screen.dart'; import 'package:cheminova/screens/update_inventory_screen.dart';
import 'package:cheminova/screens/display_sales_screen.dart'; import 'package:cheminova/screens/display_sales_screen.dart';
import 'package:cheminova/widgets/common_drawer.dart'; import 'package:cheminova/widgets/common_drawer.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cheminova/widgets/common_background.dart'; import 'package:cheminova/widgets/common_background.dart';
import 'package:cheminova/screens/daily_tasks_screen.dart'; import 'package:cheminova/screens/daily_tasks_screen.dart';
@ -278,6 +277,11 @@ class _HomePageState extends State<HomePage> {
], ],
), ),
), ),
const SizedBox(height: 12),
TextButton(
onPressed: () => throw Exception(),
child: const Text("Throw Test Exception"),
),
], ],
), ),
), ),

View File

@ -5,7 +5,7 @@ import 'package:cheminova/widgets/common_app_bar.dart';
import 'package:cheminova/widgets/common_elevated_button.dart'; import 'package:cheminova/widgets/common_elevated_button.dart';
class ProductsManualScreen extends StatelessWidget { class ProductsManualScreen extends StatelessWidget {
const ProductsManualScreen({Key? key}) : super(key: key); const ProductsManualScreen({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -178,8 +178,8 @@ class _ProductBlockState extends State<ProductBlock> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Product: ${widget.product.name}',style: TextStyle(fontSize: 16),), Text('Product: ${widget.product.name}',style: const TextStyle(fontSize: 16),),
Text('SKU: ${widget.product.sku}',style: TextStyle(fontSize: 15),), Text('SKU: ${widget.product.sku}',style: const TextStyle(fontSize: 15),),
const SizedBox(height: 8), const SizedBox(height: 8),
TextField( TextField(
controller: saleController, controller: saleController,

View File

@ -6,7 +6,9 @@ import FlutterMacOS
import Foundation import Foundation
import file_selector_macos import file_selector_macos
import firebase_analytics
import firebase_core import firebase_core
import firebase_crashlytics
import firebase_messaging import firebase_messaging
import flutter_local_notifications import flutter_local_notifications
import flutter_secure_storage_macos import flutter_secure_storage_macos
@ -15,7 +17,9 @@ import path_provider_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin"))
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))

View File

@ -173,10 +173,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: cross_file name: cross_file
sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.4+1" version: "0.3.4+2"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@ -297,6 +297,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.3+2" version: "0.9.3+2"
firebase_analytics:
dependency: "direct main"
description:
name: firebase_analytics
sha256: "064e5b57b0693305946b7caa6a80ed80a918f46804c247b6cd7ed9cd327df48f"
url: "https://pub.dev"
source: hosted
version: "11.2.1"
firebase_analytics_platform_interface:
dependency: transitive
description:
name: firebase_analytics_platform_interface
sha256: d094547c9022c404b5ca39b7209607fc80e75e39d38875f050508fa4346b3e74
url: "https://pub.dev"
source: hosted
version: "4.2.1"
firebase_analytics_web:
dependency: transitive
description:
name: firebase_analytics_web
sha256: "06dc023b0144c0df630a56b6262cc9e7d6069fe78148853d97614dbefb6ea923"
url: "https://pub.dev"
source: hosted
version: "0.5.9+1"
firebase_core: firebase_core:
dependency: "direct main" dependency: "direct main"
description: description:
@ -321,6 +345,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.17.4" version: "2.17.4"
firebase_crashlytics:
dependency: "direct main"
description:
name: firebase_crashlytics
sha256: "30260e1b8ad1464b41ca4531b44ce63d752daaf2f12c92ca6cdcd82b270abecc"
url: "https://pub.dev"
source: hosted
version: "4.0.4"
firebase_crashlytics_platform_interface:
dependency: transitive
description:
name: firebase_crashlytics_platform_interface
sha256: a75e1826d92ea4e86e4a753c7b5d64b844a362676fa653185f1581c859186d18
url: "https://pub.dev"
source: hosted
version: "3.6.40"
firebase_messaging: firebase_messaging:
dependency: "direct main" dependency: "direct main"
description: description:
@ -386,18 +426,18 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "4.0.0"
flutter_local_notifications: flutter_local_notifications:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_local_notifications name: flutter_local_notifications
sha256: dd6676d8c2926537eccdf9f72128bbb2a9d0814689527b17f92c248ff192eaf3 sha256: c500d5d9e7e553f06b61877ca6b9c8b92c570a4c8db371038702e8ce57f8a50f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "17.2.1+2" version: "17.2.2"
flutter_local_notifications_linux: flutter_local_notifications_linux:
dependency: transitive dependency: transitive
description: description:
@ -540,10 +580,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: geolocator_android name: geolocator_android
sha256: "00c7177a95823dd3ee35ef42fd8666cd27d219ae14cea472ac76a21dff43000b" sha256: "7aefc530db47d90d0580b552df3242440a10fe60814496a979aa67aa98b1fd47"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.6.0" version: "4.6.1"
geolocator_apple: geolocator_apple:
dependency: transitive dependency: transitive
description: description:
@ -628,26 +668,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: image_picker name: image_picker
sha256: b6951e25b795d053a6ba03af5f710069c99349de9341af95155d52665cb4607c sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.9" version: "1.1.2"
image_picker_android: image_picker_android:
dependency: transitive dependency: transitive
description: description:
name: image_picker_android name: image_picker_android
sha256: "8c3168469b005a6dbf5ba01f795917ae4f4e71077d3d7f2049a0d25a4760393e" sha256: c0e72ecd170b00a5590bb71238d57dc8ad22ee14c60c6b0d1a4e05cafbc5db4b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.12+8" version: "0.8.12+11"
image_picker_for_web: image_picker_for_web:
dependency: transitive dependency: transitive
description: description:
name: image_picker_for_web name: image_picker_for_web
sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0" sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "3.0.5"
image_picker_ios: image_picker_ios:
dependency: transitive dependency: transitive
description: description:
@ -700,10 +740,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: intl name: intl
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.17.0" version: "0.19.0"
io: io:
dependency: transitive dependency: transitive
description: description:
@ -756,10 +796,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "4.0.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -836,18 +876,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider name: path_provider
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.4"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: e84c8a53fe1510ef4582f118c7b4bdf15b03002b51d7c2b66983c65843d61193 sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.8" version: "2.2.9"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
@ -892,10 +932,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_android name: permission_handler_android
sha256: b29a799ca03be9f999aa6c39f7de5209482d638e6f857f6b93b0875c618b7e54 sha256: eaf2a1ec4472775451e88ca6a7b86559ef2f1d1ed903942ed135e38ea0097dca
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "12.0.7" version: "12.0.8"
permission_handler_apple: permission_handler_apple:
dependency: transitive dependency: transitive
description: description:
@ -916,10 +956,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_platform_interface name: permission_handler_platform_interface
sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20" sha256: fe0ffe274d665be8e34f9c59705441a7d248edebbe5d9e3ec2665f88b79358ea
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.1" version: "4.2.2"
permission_handler_windows: permission_handler_windows:
dependency: transitive dependency: transitive
description: description:
@ -1081,10 +1121,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: table_calendar name: table_calendar
sha256: "7f1270313c0cdb245b583ed8518982c01d4a7e95869b3c30abcbae3b642c45d0" sha256: "4ca32b2fc919452c9974abd4c6ea611a63e33b9e4f0b8c38dba3ac1f4a6549d1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.8" version: "3.1.2"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -1225,10 +1265,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.5.1" version: "5.5.3"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View File

@ -31,12 +31,12 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
table_calendar: ^3.0.0 table_calendar: ^3.0.0
image_picker: ^0.8.4+5 image_picker: ^1.1.2
cupertino_icons: ^1.0.6 cupertino_icons: ^1.0.6
flutter_svg: ^2.0.10+1 flutter_svg: ^2.0.10+1
flutter_gen: ^5.6.0 flutter_gen: ^5.6.0
pinput: ^5.0.0 pinput: ^5.0.0
intl: ^0.17.0 intl: ^0.19.0
dio: ^5.5.0+1 dio: ^5.5.0+1
flutter_secure_storage: ^9.2.2 flutter_secure_storage: ^9.2.2
provider: ^6.1.2 provider: ^6.1.2
@ -48,12 +48,14 @@ dependencies:
firebase_core: ^3.3.0 firebase_core: ^3.3.0
firebase_messaging: ^15.0.4 firebase_messaging: ^15.0.4
flutter_local_notifications: ^17.2.1+2 flutter_local_notifications: ^17.2.1+2
firebase_crashlytics: ^4.0.4
firebase_analytics: ^11.2.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^3.0.0 flutter_lints: ^4.0.0
build_runner: build_runner:
flutter_gen_runner: flutter_gen_runner: