Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What is the best approach for obtaining two different return types response model for our data source? #2

Closed
apptechxonia opened this issue Mar 16, 2023 · 14 comments
Labels
question Further information is requested

Comments

@apptechxonia
Copy link

apptechxonia commented Mar 16, 2023

Clean Architecture Version (Cubit)/lib/features/comment/data/datasources

Suppose we have two distinct response models for the same data source, how can we obtain them? Is it necessary to create a new data_source dart file for each new model, or can we organize multiple response models using the same data source?

  1. User Model
  2. UserType Model
@SinaSys
Copy link
Owner

SinaSys commented Mar 16, 2023

I did not understand what you meant. Could you please provide more details?
What do you mean by model classes returning two values?

@apptechxonia
Copy link
Author

apptechxonia commented Mar 20, 2023

@SinaSys

I need to make two API calls to the same remote data source, but each API will provide a different response - one for User and one for UserType. Both responses correspond to different models.

For example:

abstract class UserRemoteDataSource {
  Future<List<User>> getUser(int userId);

  Future<List<UserType>> getUserType(int user);
}

How can I accomplish this using the same data source file? Should I create separate remote data sources for each response?

@apptechxonia apptechxonia changed the title What is the best approach for obtaining two different return types model for our data source? What is the best approach for obtaining two different return types response model for our data source? Mar 20, 2023
@SinaSys
Copy link
Owner

SinaSys commented Mar 20, 2023

There is no need to define any new data sources. You can use the current user data source. It is not necessary to define a separate data source for each model class. Once you define the model class (UserType), you can apply code lines to various layers of the architecture. Upon examining the code, I don't think it will be very challenging. You need to modify the repository (abstract and concrete), remote data source, and presentation parts.

@SinaSys SinaSys added the question Further information is requested label Mar 20, 2023
@apptechxonia
Copy link
Author

apptechxonia commented Mar 20, 2023

Can you provide me with some code snippets of how I can do this if we have multiple responses in the same remote data source?

In order to quickly solve my work, I have created two data sources, Cubit and Repository, for each response type to handle the case. However, this approach may not be suitable if I have a larger number of APIs on the same data source. If I maintain the same method for one module with 20 API calls, I will have to produce distinct data sources, repositories, and cubits, which could become a problem in the future. Therefore, I am posting this question here to find a more general solution.

@SinaSys
Copy link
Owner

SinaSys commented Mar 21, 2023

Place the codes related to the api call inside the UserRemoteDataSourceImpl class itself:

class UserRemoteDataSourceImpl with ApiHelper<User> implements UserRemoteDataSource {
   final DioClient dioClient = getIt<DioClient>();
  
   @override
   Future<UserType> getUserType() async {
     final Response response = await dioClient.dio.get("...");
     UserType userTypes = UserType.fromJson(json.encode(response.data));
     return userTypes;
    
     /// Please notice that you should define fromJson factory constructor
     /// inside your UserType class
   }
}


abstract class UserRemoteDataSource {
   Future<List<User>> getUsers({Gender? gender, UserStatus? status});

//Add this line
   Future<UserType> getUserType();

   Future<bool> createUser(User user);

   Future<bool> updateUser(User user);

   Future<bool> deleteUser(User user);
}

Also, the repository classes should be changed as follows.

abstract class UserRepository {
   Future<ApiResult<List<User>>> getUsers({Gender? gender, UserStatus? status});

//Add this line
   Future<ApiResult<UserType>> getUserType();

   Future<ApiResult<bool>> createUser(User user);

   Future<ApiResult<bool>> updateUser(User user);

   Future<ApiResult<bool>> deleteUser(User user);
}


class UserRepositoryImpl extends UserRepository with RepositoryHelper<User> {
   final UserRemoteDataSource remoteDataSource;

   UserRepositoryImpl({required this.remoteDataSource});

     ............

   @override
   Future<ApiResult<UserType>> getUserType()async {
     try {
       final UserType items = await remoteDataSource.getUserType();
       return ApiResult.success(items);
     } on DioError catch (e) {
       final errorMessage = DioExceptions.fromDioError(e).toString();
       return ApiResult.failure(errorMessage);
     }
   }
}

In fact, in this solution, we ignore the Helper classes ApiHelper and RepositoryHelper, and we must write the corresponding methods in the child classes instead of Helper classes. and we don't need to define any additional datasources.

@SinaSys
Copy link
Owner

SinaSys commented Mar 21, 2023

I would suggest deleting the classes ApiHelper and RepositoryHelper for the sake of simplicity and rewriting the code accordingly. These classes are generic helper classes that have been written to reduce code duplication. Removing them can make it easier for you to understand the code.

@apptechxonia
Copy link
Author

apptechxonia commented Mar 21, 2023

Do we need a cubit file separate to receive two different response data?


class UserCubit extends GenericCubit<User> {
  final UserUseCase userUseCase;
  final UserTypeUseCase userTypeUseCase;

  UserCubit({
    required this.userUseCase,
    required this.userTypeUseCase,
  });

  Future<void> getUser() async {
    getSingleItem(userUseCase.call(UserParams()));
  }

  Future<void> getUserType() async {
    getSingleItem(userTypeUseCase.call(UserTypeParams()));
  }
}

I am getting this error while write the getSingleItem(userTypeUseCase.call(UserTypeParams()));

The argument type 'Future<ApiResult<UserType>>' can't be assigned to the parameter type 'Future<ApiResult<User>>'

How to solve this issue on the cubit file?

If I removed "User" from here GenericCubit then it creates a problem at the view level. How can one identify the API calls that a cubit state generates for various response models?

@apptechxonia
Copy link
Author

I would suggest deleting the classes ApiHelper and RepositoryHelper for the sake of simplicity and rewriting the code accordingly. These classes are generic helper classes that have been written to reduce code duplication. Removing them can make it easier for you to understand the code.

Can we utilize API helper and repository helper to handle multiple responses? Implementing a helper class would simplify potential future issues. Currently, writing the code for each call could cause problems if modifications are necessary.

@SinaSys
Copy link
Owner

SinaSys commented Mar 21, 2023

Do we need a cubit file separate to receive two different response data?


class UserCubit extends GenericCubit<User> {
  final UserUseCase userUseCase;
  final UserTypeUseCase userTypeUseCase;

  UserCubit({
    required this.userUseCase,
    required this.userTypeUseCase,
  });

  Future<void> getUser() async {
    getSingleItem(userUseCase.call(UserParams()));
  }

  Future<void> getUserType() async {
    getSingleItem(userTypeUseCase.call(UserTypeParams()));
  }
}

I am getting this error while write the getSingleItem(userTypeUseCase.call(UserTypeParams()));

The argument type 'Future<ApiResult<UserType>>' can't be assigned to the parameter type 'Future<ApiResult<User>>'

How to solve this issue on the cubit file?

If I removed "User" from here GenericCubit then it creates a problem at the view level. How can one identify the API calls that a cubit state generates for various response models?

As I mentioned earlier, the same thing that should be done for ApiHelper and RepositoryHelper should also be done for GenericCubit.

That is, the code related to UserType should be transferred from GenericCubit to UserCubit. as follows:

class UserCubit extends GenericCubit<User> {
  
  UserCubit({...});
  
  Future<void> getUserType() async {
    emit(GenericCubitState.loading());
    ApiResult failureOrSuccess = await userTypeUseCase.call(UserTypeParams());
    failureOrSuccess.when(
      failure: (String failure) {
        emit(GenericCubitState.failure(failure));
      },
      success: (_) {
        emit(GenericCubitState.success(null));
      },
    );
  }
}

The getSingleItem method is defined in the GenericCubit and is only capable of retrieving a User object, not a UserType object.

@SinaSys
Copy link
Owner

SinaSys commented Mar 21, 2023

I would suggest deleting the classes ApiHelper and RepositoryHelper for the sake of simplicity and rewriting the code accordingly. These classes are generic helper classes that have been written to reduce code duplication. Removing them can make it easier for you to understand the code.

Can we utilize API helper and repository helper to handle multiple responses? Implementing a helper class would simplify potential future issues. Currently, writing the code for each call could cause problems if modifications are necessary.

Yes it is possible. There are several solutions

  1. Changing the ApiHelper class and converting 1 type parameter to 2 type parameters:
abstract class ApiHelper<A,B>{
   late final A firstData;
   late final B secondData;
   ...........
}


class UserRemoteDataSourceImpl with ApiHelper<User,UserType> implements UserRemoteDataSource {...}

But I do not recommend this solution. because you have to define type parameter for each model class.

  1. Creating a base class and inheriting the user class and user type from it and placing it instead of a parameter in the ApiHelper class
abstract class UserBase{}

class User extends UserBase{}

class UserType extends UserBase{}

class UserRemoteDataSourceImpl with ApiHelper<UserBase> implements UserRemoteDataSource{..}

@apptechxonia
Copy link
Author

apptechxonia commented Apr 17, 2023

class UserCubit extends GenericCubit<User> {
  
  UserCubit({...});
  
  Future<void> getUserType() async {
    emit(GenericCubitState.loading());
    ApiResult failureOrSuccess = await userTypeUseCase.call(UserTypeParams());
    failureOrSuccess.when(
      failure: (String failure) {
        emit(GenericCubitState.failure(failure));
      },
      success: (_) {
        emit(GenericCubitState.success(null));
      },
    );
  }
}

After invoking getUserType() and listening on a BlocListener, I received a null value. How to fix this problem?

@SinaSys
Copy link
Owner

SinaSys commented Apr 17, 2023

class UserCubit extends GenericCubit<User> {
  
  UserCubit({...});
  
  Future<void> getUserType() async {
    emit(GenericCubitState.loading());
    ApiResult failureOrSuccess = await userTypeUseCase.call(UserTypeParams());
    failureOrSuccess.when(
      failure: (String failure) {
        emit(GenericCubitState.failure(failure));
      },
      success: (_) {
        emit(GenericCubitState.success(null));
      },
    );
  }
}

After invoking getUserType() and listening on a BlocListener, I received a null value. How to fix this problem?

It seems that you intended to use HTTP method 'GET', which means that some data should be returned from the server. However, your code does not match with the 'GET' method. It may be useful for other HTTP methods, but not for 'GET'. Please modify your code accordingly.

 Future<void> getUserTypeasync {
   emit(GenericBlocState.loading());
    ApiResult failureOrSuccess = await userTypeUseCase.call(UserTypeParams());


   failureOrSuccess.when(
     failure: (String failure) async {
       emit(GenericBlocState.failure(failure));
     },
     success: (List<..> items) async {
         emit(GenericBlocState.success(items)); 
     },
   );
 }

@apptechxonia
Copy link
Author

apptechxonia commented Apr 18, 2023

I apologize for my mistake. As a newcomer to flutter and flutter state management, I sometimes become confused. Thank you for helping me identify my mistake.

I am facing another issue. After implementing the updated code, I am able to successfully call both the User and UserType APIs. The User API is called first, followed by the UserType API. However, when I try to retrieve the data from both APIs on the next screen, I encounter an issue: the cubit only displays the data from the last API call.

What is the best way to handle this situation? Currently, I am storing the data in a variable and passing it to the next screen. Is this a good approach or is there a better way to handle this?

class UserCubit extends GenericCubit {
  UserResponse? userResponse;
  UserCubit({...});
  Future<void> getUser() async {
    getSingleItem(userUseCase.call(UserParams())); // UserResponse
  }
  Future<void> getUserType() async {
    getSingleItem(userTypeUseCase.call(UserTypeParams())); // UserTypeResponse
  }
}

I have removed the UserResponse from GenericCubit. Unremoving give an error occurs indicating that 'UserTypeResponse?' argument type cannot be assigned to the 'UserResponse' parameter type..

To display data on the next screen, I am storing the data from the first API call in the userResponse variable and retrieving the required data from it. Is this a good approach, or is there any alternative way to obtain all the API call data from the same cubit file?

@SinaSys
Copy link
Owner

SinaSys commented Apr 19, 2023

I apologize for my mistake. As a newcomer to flutter and flutter state management, I sometimes become confused. Thank you for helping me identify my mistake.

I am facing another issue. After implementing the updated code, I am able to successfully call both the User and UserType APIs. The User API is called first, followed by the UserType API. However, when I try to retrieve the data from both APIs on the next screen, I encounter an issue: the cubit only displays the data from the last API call.

What is the best way to handle this situation? Currently, I am storing the data in a variable and passing it to the next screen. Is this a good approach or is there a better way to handle this?

class UserCubit extends GenericCubit {
  UserResponse? userResponse;
  UserCubit({...});
  Future<void> getUser() async {
    getSingleItem(userUseCase.call(UserParams())); // UserResponse
  }
  Future<void> getUserType() async {
    getSingleItem(userTypeUseCase.call(UserTypeParams())); // UserTypeResponse
  }
}

I have removed the UserResponse from GenericCubit. Unremoving give an error occurs indicating that 'UserTypeResponse?' argument type cannot be assigned to the 'UserResponse' parameter type..

To display data on the next screen, I am storing the data from the first API call in the userResponse variable and retrieving the required data from it. Is this a good approach, or is there any alternative way to obtain all the API call data from the same cubit file?

I believe that if you post your problem on Stack Overflow or this Telegram group, you are more likely to receive a faster response.

Yes, you can.

In general, there are different solutions available that can be used depending on your requirements.

• The easiest way is passing data through constructor.

• The bloc class in Flutter acts as a central storage. This means that by defining a variable in the bloc and storing data in it, you can always retrieve updated data in all the screens that use that bloc.

• Another solution is to define a variable or getter inside the state class of the bloc (Generic state) and retrieve it in the desired screen.

Your solution appears to be correct, but I am unsure about the reason for the error since your code has been modified from the original code in the repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants