From 9133070c7dd413e4a843b001bcb99191c37c06b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAmarendra=E2=80=9D?= <“amarendrapratapsingh.2004@gmail.com”> Date: Sat, 1 Nov 2025 01:52:46 +0530 Subject: [PATCH 1/2] Implement auth inheritance feature: Allow requests to inherit auth credentials from environments --- lib/providers/collection_providers.dart | 20 ++- lib/providers/environment_providers.dart | 2 + .../common_widgets/auth/auth_page.dart | 115 +++++++++++------- .../envvar/environment_auth_editor.dart | 73 +++++++++++ lib/screens/envvar/environment_editor.dart | 52 +++++--- .../request_pane/request_auth.dart | 12 +- lib/utils/envvar_utils.dart | 9 ++ .../lib/models/environment_model.dart | 2 + .../lib/models/environment_model.g.dart | 7 +- packages/better_networking/lib/consts.dart | 8 ++ .../lib/models/http_request_model.dart | 1 + test/models/environment_models.dart | 40 +++++- test/models/environment_models_test.dart | 26 ++++ 13 files changed, 302 insertions(+), 65 deletions(-) create mode 100644 lib/screens/envvar/environment_auth_editor.dart diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index 51de409bc..4e36ac1c3 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -216,6 +216,7 @@ class CollectionStateNotifier String? id, HTTPVerb? method, AuthModel? authModel, + AuthInheritanceType? authInheritanceType, String? url, String? name, String? description, @@ -275,6 +276,7 @@ class CollectionStateNotifier headers: headers ?? currentHttpRequestModel.headers, params: params ?? currentHttpRequestModel.params, authModel: authModel ?? currentHttpRequestModel.authModel, + authInheritanceType: authInheritanceType ?? currentHttpRequestModel.authInheritanceType, isHeaderEnabledList: isHeaderEnabledList ?? currentHttpRequestModel.isHeaderEnabledList, isParamEnabledList: @@ -628,9 +630,25 @@ class CollectionStateNotifier HttpRequestModel httpRequestModel) { var envMap = ref.read(availableEnvironmentVariablesStateProvider); var activeEnvId = ref.read(activeEnvironmentIdStateProvider); + var environments = ref.read(environmentsStateNotifierProvider); + + // Handle auth inheritance + HttpRequestModel processedRequestModel = httpRequestModel; + if (httpRequestModel.authInheritanceType == AuthInheritanceType.environment && + activeEnvId != null && + environments != null && + environments[activeEnvId] != null) { + + final environment = environments[activeEnvId]!; + if (environment.defaultAuthModel != null) { + processedRequestModel = httpRequestModel.copyWith( + authModel: environment.defaultAuthModel, + ); + } + } return substituteHttpRequestModel( - httpRequestModel, + processedRequestModel, envMap, activeEnvId, ); diff --git a/lib/providers/environment_providers.dart b/lib/providers/environment_providers.dart index b431930ba..b8137ba6f 100644 --- a/lib/providers/environment_providers.dart +++ b/lib/providers/environment_providers.dart @@ -126,11 +126,13 @@ class EnvironmentsStateNotifier String id, { String? name, List? values, + AuthModel? defaultAuthModel, }) { final environment = state![id]!; final updatedEnvironment = environment.copyWith( name: name ?? environment.name, values: values ?? environment.values, + defaultAuthModel: defaultAuthModel ?? environment.defaultAuthModel, ); state = { ...state!, diff --git a/lib/screens/common_widgets/auth/auth_page.dart b/lib/screens/common_widgets/auth/auth_page.dart index 65d962595..916994b65 100644 --- a/lib/screens/common_widgets/auth/auth_page.dart +++ b/lib/screens/common_widgets/auth/auth_page.dart @@ -1,6 +1,7 @@ import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:apidash_core/apidash_core.dart'; +import 'package:better_networking/better_networking.dart'; import 'api_key_auth_fields.dart'; import 'basic_auth_fields.dart'; import 'bearer_auth_fields.dart'; @@ -12,16 +13,20 @@ import 'oauth2_field.dart'; class AuthPage extends StatelessWidget { final AuthModel? authModel; + final AuthInheritanceType? authInheritanceType; final bool readOnly; final Function(APIAuthType? newType)? onChangedAuthType; final Function(AuthModel? model)? updateAuthData; + final Function(AuthInheritanceType? newType)? onChangedAuthInheritanceType; const AuthPage({ super.key, this.authModel, + this.authInheritanceType, this.readOnly = false, this.onChangedAuthType, this.updateAuthData, + this.onChangedAuthInheritanceType, }); @override @@ -41,61 +46,79 @@ class AuthPage extends StatelessWidget { SizedBox( height: 8, ), - ADPopupMenu( - value: authModel?.type.displayType, - values: APIAuthType.values - .map((type) => (type, type.displayType)) - .toList(), - tooltip: kTooltipSelectAuth, - isOutlined: true, - onChanged: readOnly ? null : onChangedAuthType, - ), - const SizedBox(height: 48), - switch (authModel?.type) { - APIAuthType.basic => BasicAuthFields( - readOnly: readOnly, - authData: authModel, - updateAuth: updateAuthData, - ), - APIAuthType.bearer => BearerAuthFields( - readOnly: readOnly, - authData: authModel, - updateAuth: updateAuthData, + // Auth inheritance selection + if (!readOnly) ...[ + ADPopupMenu( + value: (authInheritanceType ?? AuthInheritanceType.none).displayType, + values: AuthInheritanceType.values + .map((type) => (type, type.displayType)) + .toList(), + tooltip: "Select auth inheritance type", + isOutlined: true, + onChanged: onChangedAuthInheritanceType, + ), + const SizedBox(height: 16), + ], + // Show auth type selection only when not inheriting + if (authInheritanceType != AuthInheritanceType.environment) ...[ + ADPopupMenu( + value: authModel?.type.displayType, + values: APIAuthType.values + .map((type) => (type, type.displayType)) + .toList(), + tooltip: kTooltipSelectAuth, + isOutlined: true, + onChanged: readOnly ? null : onChangedAuthType, + ), + const SizedBox(height: 48), + switch (authModel?.type) { + APIAuthType.basic => BasicAuthFields( + readOnly: readOnly, + authData: authModel, + updateAuth: updateAuthData, + ), + APIAuthType.bearer => BearerAuthFields( + readOnly: readOnly, + authData: authModel, + updateAuth: updateAuthData, ), - APIAuthType.apiKey => ApiKeyAuthFields( - readOnly: readOnly, - authData: authModel, - updateAuth: updateAuthData, + APIAuthType.apiKey => ApiKeyAuthFields( + readOnly: readOnly, + authData: authModel, + updateAuth: updateAuthData, ), - APIAuthType.jwt => JwtAuthFields( - readOnly: readOnly, - authData: authModel, - updateAuth: updateAuthData, + APIAuthType.jwt => JwtAuthFields( + readOnly: readOnly, + authData: authModel, + updateAuth: updateAuthData, ), - APIAuthType.digest => DigestAuthFields( - readOnly: readOnly, - authData: authModel, - updateAuth: updateAuthData, + APIAuthType.digest => DigestAuthFields( + readOnly: readOnly, + authData: authModel, + updateAuth: updateAuthData, ), APIAuthType.oauth1 => OAuth1Fields( - readOnly: readOnly, - authData: authModel, - updateAuth: updateAuthData, + readOnly: readOnly, + authData: authModel, + updateAuth: updateAuthData, ), - APIAuthType.oauth2 => OAuth2Fields( - readOnly: readOnly, - authData: authModel, - updateAuth: updateAuthData, + APIAuthType.oauth2 => OAuth2Fields( + readOnly: readOnly, + authData: authModel, + updateAuth: updateAuthData, ), - APIAuthType.none => - Text(readOnly ? kMsgNoAuth : kMsgNoAuthSelected), - _ => Text(readOnly - ? "${authModel?.type.name} $kMsgAuthNotSupported" - : kMsgNotImplemented), - } + APIAuthType.none => + Text(readOnly ? kMsgNoAuth : kMsgNoAuthSelected), + _ => Text(readOnly + ? "${authModel?.type.name} $kMsgAuthNotSupported" + : kMsgNotImplemented), + } + ] else ...[ + const Text("Using environment inherited authentication"), + ] ], ), ), ); } -} +} \ No newline at end of file diff --git a/lib/screens/envvar/environment_auth_editor.dart b/lib/screens/envvar/environment_auth_editor.dart new file mode 100644 index 000000000..b2e7cee41 --- /dev/null +++ b/lib/screens/envvar/environment_auth_editor.dart @@ -0,0 +1,73 @@ +import 'package:apidash_design_system/apidash_design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:apidash/providers/providers.dart'; +import 'package:apidash/widgets/widgets.dart'; +import 'package:apidash/consts.dart'; +import 'package:better_networking/better_networking.dart'; +import '../common_widgets/common_widgets.dart'; + +class EnvironmentAuthEditor extends ConsumerWidget { + const EnvironmentAuthEditor({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final environment = ref.watch(selectedEnvironmentModelProvider); + + if (environment == null) { + return const SizedBox.shrink(); + } + + final defaultAuthModel = environment.defaultAuthModel; + + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Default Authentication", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(height: 16), + const Text( + "Set default authentication for all requests using this environment", + style: TextStyle( + fontSize: 14, + ), + ), + const SizedBox(height: 24), + AuthPage( + authModel: defaultAuthModel, + readOnly: false, + onChangedAuthType: (newType) { + if (newType != null) { + final updatedAuthModel = defaultAuthModel?.copyWith(type: newType) ?? + AuthModel(type: newType); + ref + .read(environmentsStateNotifierProvider.notifier) + .updateEnvironment( + environment.id, + values: environment.values, + defaultAuthModel: updatedAuthModel, + ); + } + }, + updateAuthData: (model) { + ref + .read(environmentsStateNotifierProvider.notifier) + .updateEnvironment( + environment.id, + values: environment.values, + defaultAuthModel: model, + ); + }, + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/envvar/environment_editor.dart b/lib/screens/envvar/environment_editor.dart index 4ca07b570..d8ded1bbe 100644 --- a/lib/screens/envvar/environment_editor.dart +++ b/lib/screens/envvar/environment_editor.dart @@ -6,6 +6,7 @@ import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/consts.dart'; import '../common_widgets/common_widgets.dart'; import './editor_pane/variables_pane.dart'; +import 'environment_auth_editor.dart'; class EnvironmentEditor extends ConsumerWidget { const EnvironmentEditor({super.key}); @@ -15,6 +16,7 @@ class EnvironmentEditor extends ConsumerWidget { final id = ref.watch(selectedEnvironmentIdStateProvider); final name = ref .watch(selectedEnvironmentModelProvider.select((value) => value?.name)); + return Padding( padding: context.isMediumWindow ? kPb10 @@ -84,24 +86,44 @@ class EnvironmentEditor extends ConsumerWidget { borderRadius: kBorderRadius12, ), elevation: 0, - child: const Padding( - padding: kPv6, + child: DefaultTabController( + length: 2, child: Column( children: [ - kHSpacer40, - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(width: 30), - Text("Variable"), - SizedBox(width: 30), - Text("Value"), - SizedBox(width: 40), + TabBar( + tabs: const [ + Tab(text: "Variables"), + Tab(text: "Auth"), ], ), - kHSpacer40, - Divider(), - Expanded(child: EditEnvironmentVariables()) + const Expanded( + child: TabBarView( + children: [ + Padding( + padding: kPv6, + child: Column( + children: [ + kHSpacer40, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: 30), + Text("Variable"), + SizedBox(width: 30), + Text("Value"), + SizedBox(width: 40), + ], + ), + kHSpacer40, + Divider(), + Expanded(child: EditEnvironmentVariables()) + ], + ), + ), + EnvironmentAuthEditor(), + ], + ), + ), ], ), ), @@ -112,4 +134,4 @@ class EnvironmentEditor extends ConsumerWidget { ), ); } -} +} \ No newline at end of file diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_auth.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_auth.dart index daf0e0d10..6147c4b0d 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_auth.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_auth.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash_core/apidash_core.dart'; +import 'package:better_networking/better_networking.dart'; import 'package:apidash/providers/providers.dart'; import '../../../../common_widgets/common_widgets.dart'; @@ -24,9 +25,11 @@ class EditAuthType extends ConsumerWidget { request?.httpRequestModel?.authModel?.type ?? APIAuthType.none), ); final currentAuthData = selectedRequest.httpRequestModel?.authModel; + final currentAuthInheritanceType = selectedRequest.httpRequestModel?.authInheritanceType; return AuthPage( authModel: currentAuthData, + authInheritanceType: currentAuthInheritanceType, readOnly: readOnly, onChangedAuthType: (newType) { final selectedRequest = ref.read(selectedRequestModelProvider); @@ -48,6 +51,13 @@ class EditAuthType extends ConsumerWidget { authModel: model, ); }, + onChangedAuthInheritanceType: (newType) { + if (newType != null) { + ref.read(collectionStateNotifierProvider.notifier).update( + authInheritanceType: newType, + ); + } + }, ); } -} +} \ No newline at end of file diff --git a/lib/utils/envvar_utils.dart b/lib/utils/envvar_utils.dart index f817dd967..f289a426b 100644 --- a/lib/utils/envvar_utils.dart +++ b/lib/utils/envvar_utils.dart @@ -70,6 +70,15 @@ HttpRequestModel substituteHttpRequestModel( combinedEnvVarMap[variable.key] = variable.value; } + // Handle auth inheritance + AuthModel? finalAuthModel = httpRequestModel.authModel; + if (httpRequestModel.authInheritanceType == AuthInheritanceType.environment && + activeEnvironmentId != null) { + // Get the environment model to access its default auth + // This would require access to the full environment models, not just variables + // For now, we'll handle this in the request processing where we have full environment access + } + var newRequestModel = httpRequestModel.copyWith( url: substituteVariables(httpRequestModel.url, combinedEnvVarMap)!, headers: httpRequestModel.headers?.map((header) { diff --git a/packages/apidash_core/lib/models/environment_model.dart b/packages/apidash_core/lib/models/environment_model.dart index 9242d4d59..9b26d1554 100644 --- a/packages/apidash_core/lib/models/environment_model.dart +++ b/packages/apidash_core/lib/models/environment_model.dart @@ -1,4 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:better_networking/better_networking.dart'; import '../consts.dart'; part 'environment_model.freezed.dart'; @@ -15,6 +16,7 @@ class EnvironmentModel with _$EnvironmentModel { required String id, @Default("") String name, @Default([]) List values, + AuthModel? defaultAuthModel, // Add default auth model for inheritance }) = _EnvironmentModel; factory EnvironmentModel.fromJson(Map json) => diff --git a/packages/apidash_core/lib/models/environment_model.g.dart b/packages/apidash_core/lib/models/environment_model.g.dart index dd6b98e91..d8e5f1430 100644 --- a/packages/apidash_core/lib/models/environment_model.g.dart +++ b/packages/apidash_core/lib/models/environment_model.g.dart @@ -15,6 +15,10 @@ _$EnvironmentModelImpl _$$EnvironmentModelImplFromJson(Map json) => Map.from(e as Map))) .toList() ?? const [], + defaultAuthModel: json['defaultAuthModel'] == null + ? null + : AuthModel.fromJson( + Map.from(json['defaultAuthModel'] as Map)), ); Map _$$EnvironmentModelImplToJson( @@ -23,6 +27,7 @@ Map _$$EnvironmentModelImplToJson( 'id': instance.id, 'name': instance.name, 'values': instance.values.map((e) => e.toJson()).toList(), + 'defaultAuthModel': instance.defaultAuthModel?.toJson(), }; _$EnvironmentVariableModelImpl _$$EnvironmentVariableModelImplFromJson( @@ -48,4 +53,4 @@ Map _$$EnvironmentVariableModelImplToJson( const _$EnvironmentVariableTypeEnumMap = { EnvironmentVariableType.variable: 'variable', EnvironmentVariableType.secret: 'secret', -}; +}; \ No newline at end of file diff --git a/packages/better_networking/lib/consts.dart b/packages/better_networking/lib/consts.dart index 002cc2e7f..caf9b9a34 100644 --- a/packages/better_networking/lib/consts.dart +++ b/packages/better_networking/lib/consts.dart @@ -180,3 +180,11 @@ const kCodeCharsPerLineLimit = 200; const kHeaderContentType = "Content-Type"; const kHeaderWwwAuthenticate = 'www-authenticate'; const kMsgRequestCancelled = 'Request Cancelled'; + +enum AuthInheritanceType { + none("None"), + environment("Environment"); + + const AuthInheritanceType(this.displayType); + final String displayType; +} diff --git a/packages/better_networking/lib/models/http_request_model.dart b/packages/better_networking/lib/models/http_request_model.dart index 3443e3d6c..6792378e3 100644 --- a/packages/better_networking/lib/models/http_request_model.dart +++ b/packages/better_networking/lib/models/http_request_model.dart @@ -20,6 +20,7 @@ class HttpRequestModel with _$HttpRequestModel { List? headers, List? params, @Default(AuthModel(type: APIAuthType.none)) AuthModel? authModel, + AuthInheritanceType? authInheritanceType, // Add auth inheritance type List? isHeaderEnabledList, List? isParamEnabledList, @Default(ContentType.json) ContentType bodyContentType, diff --git a/test/models/environment_models.dart b/test/models/environment_models.dart index e23f659bd..7300465fa 100644 --- a/test/models/environment_models.dart +++ b/test/models/environment_models.dart @@ -1,5 +1,6 @@ import 'package:apidash/consts.dart'; import 'package:apidash_core/apidash_core.dart'; +import 'package:better_networking/better_networking.dart'; /// Global environment model const globalEnvironment = EnvironmentModel( @@ -59,6 +60,24 @@ const environmentModel2 = EnvironmentModel( ], ); +/// Environment model with default auth +const environmentModelWithAuth = EnvironmentModel( + id: 'environmentIdWithAuth', + name: 'Development With Auth', + values: [ + EnvironmentVariableModel( + key: 'key1', + value: 'value1', + type: EnvironmentVariableType.variable, + enabled: true, + ), + ], + defaultAuthModel: AuthModel( + type: APIAuthType.bearer, + bearer: AuthBearerModel(token: 'test-token'), + ), +); + /// Basic Environment Variable const environmentVariableModel1 = EnvironmentVariableModel( key: 'key1', @@ -126,6 +145,25 @@ const environmentModel2Json = { ], }; +const environmentModelWithAuthJson = { + 'id': 'environmentIdWithAuth', + 'name': 'Development With Auth', + 'values': [ + { + 'key': 'key1', + 'value': 'value1', + 'type': 'variable', + 'enabled': true, + }, + ], + 'defaultAuthModel': { + 'type': 'bearer', + 'bearer': { + 'token': 'test-token', + }, + }, +}; + const environmentVariableModel1Json = { 'key': 'key1', 'value': 'value1', @@ -138,4 +176,4 @@ const environmentVariableModel2Json = { 'value': 'value1', 'type': 'secret', 'enabled': true, -}; +}; \ No newline at end of file diff --git a/test/models/environment_models_test.dart b/test/models/environment_models_test.dart index f4ab1e313..92c1d2379 100644 --- a/test/models/environment_models_test.dart +++ b/test/models/environment_models_test.dart @@ -14,11 +14,29 @@ void main() { expect(environmentModel.name, 'Development'); }); + test("Testing EnvironmentModel copyWith with defaultAuthModel", () { + var environmentModel = environmentModelWithAuth; + final newAuthModel = AuthModel( + type: APIAuthType.basic, + basic: AuthBasicAuthModel(username: 'test', password: 'pass'), + ); + final environmentModelcopyWith = + environmentModel.copyWith(defaultAuthModel: newAuthModel); + expect(environmentModelcopyWith.defaultAuthModel, newAuthModel); + // original model unchanged + expect(environmentModel.defaultAuthModel?.type, APIAuthType.bearer); + }); + test("Testing EnvironmentModel toJson", () { var environmentModel = environmentModel1; expect(environmentModel.toJson(), environmentModel1Json); }); + test("Testing EnvironmentModel toJson with defaultAuthModel", () { + var environmentModel = environmentModelWithAuth; + expect(environmentModel.toJson(), environmentModelWithAuthJson); + }); + test("Testing EnvironmentModel fromJson", () { var environmentModel = environmentModel1; final modelFromJson = EnvironmentModel.fromJson(environmentModel1Json); @@ -39,6 +57,14 @@ void main() { ]); }); + test("Testing EnvironmentModel fromJson with defaultAuthModel", () { + var environmentModel = environmentModelWithAuth; + final modelFromJson = EnvironmentModel.fromJson(environmentModelWithAuthJson); + expect(modelFromJson, environmentModel); + expect(modelFromJson.defaultAuthModel?.type, APIAuthType.bearer); + expect(modelFromJson.defaultAuthModel?.bearer?.token, 'test-token'); + }); + test("Testing EnvironmentModel getters", () { var environmentModel = environmentModel1; expect(environmentModel.values, const [ From 4505b21764cbd5d2cd102a7e9164c3827670b699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAmarendra=E2=80=9D?= <“amarendrapratapsingh.2004@gmail.com”> Date: Sat, 1 Nov 2025 02:06:05 +0530 Subject: [PATCH 2/2] Fix missing imports for auth inheritance feature --- lib/providers/collection_providers.dart | 1 + lib/providers/environment_providers.dart | 1 + lib/utils/envvar_utils.dart | 1 + 3 files changed, 3 insertions(+) diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index 4e36ac1c3..27ebb06de 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/consts.dart'; import 'package:apidash/terminal/terminal.dart'; +import 'package:better_networking/better_networking.dart'; import 'providers.dart'; import '../models/models.dart'; import '../services/services.dart'; diff --git a/lib/providers/environment_providers.dart b/lib/providers/environment_providers.dart index b8137ba6f..bd2decf83 100644 --- a/lib/providers/environment_providers.dart +++ b/lib/providers/environment_providers.dart @@ -2,6 +2,7 @@ import 'package:apidash/consts.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/utils/file_utils.dart'; import 'package:apidash_core/apidash_core.dart'; +import 'package:better_networking/better_networking.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../services/services.dart' show hiveHandler, HiveHandler; diff --git a/lib/utils/envvar_utils.dart b/lib/utils/envvar_utils.dart index f289a426b..e47fd1c19 100644 --- a/lib/utils/envvar_utils.dart +++ b/lib/utils/envvar_utils.dart @@ -1,5 +1,6 @@ import 'package:apidash_core/apidash_core.dart'; import 'package:apidash/consts.dart'; +import 'package:better_networking/better_networking.dart'; String getEnvironmentTitle(String? name) { if (name == null || name.trim() == "") {