I have a simple app, which shows a list of movies. When a user clicks a button it should refresh the list, which means to go to Retrofit again and fetch the new list.
The problem is, that this refresh doesn't work. It only fetches the list once, when I open the app.
public class MoviesViewModel extends AndroidViewModel {
private MutableLiveData<Resource<List<Movie>>> mLastdMovieListObservable;
private Repository mRepository;
public MoviesViewModel(Application application) {
super(application);
mRepository = new Repository();
}
public LiveData<Resource<List<Movie>>> getMovieListLiveData() {
if (mLastdMovieListObservable == null) {
mLastdMovieListObservable = mRepository.getMovieList();
}
return mLastdMovieListObservable;
}
}
and the Repository :
public MutableLiveData<Resource<List<Movie>>> getMovieList() {
final MutableLiveData<Resource<List<Movie>>> data = new MutableLiveData<>();
data.setValue(Resource.loading(null));
mInterface.getMovies(.....).enqueue(new Callback<MovieListResponse>() {
#Override
public void onResponse(Call<MovieListResponse> call, Response<MovieListResponse> response) {
if (response.isSuccessful()) {
data.postValue(Resource.success(response.body().getResult()));
} else {
data.postValue(Resource.error(response.message(), null));
}
}
#Override
public void onFailure(Call<MovieListResponse> call, Throwable t) {
data.postValue(Resource.error(t.getMessage(), null));
}
});
return data;
}
Then in the fragment it's just :
mMoviesViewModel.getMovieListLiveData().observe(this, moviesResource -> {
if (moviesResource.status == Status.SUCCESS) {
.....
.....
then the button click part, when I need to fetch the list again, I'm not sure how to implement it.
Anything I tried didn't work. the Repository returned the data as null right away.
You can use retry approach and I changed your code quickly for an example of retry and check this source for better code.LINK
Transformations.switchMap listens to retry and when it changed, swithMap will be call again.And in UI ViewModel should be config like below but in button you need to call just retry.
mMoviesViewModel.getMovieListLiveData().observe(this, moviesResource -> {
if (moviesResource.status == Status.SUCCESS) {
.....
.....
And in button:
mMoviesViewModel.retry()
Also ViewModel:
public class MoviesViewModel extends AndroidViewModel {
private MutableLiveData<Resource<List<Movie>>> mLastdMovieListObservable;
private Repository mRepository;
private Boolean mRetry = false;
public MoviesViewModel(Application application) {
super(application);
mRepository = new Repository();
mLastdMovieListObservable = Transformations.switchMap(mRetry){
mLastdMovieListObservable = mRepository.getMovieList()
}
}
public LiveData<Resource<List<Movie>>> getMovieListLiveData() {
return mLastdMovieListObservable;
}
public void retry(){
mRetry.setValue(true)
}}
Changes for movie types can be like below:
public class MoviesViewModel extends AndroidViewModel {
private MutableLiveData<Resource<List<Movie>>> mLastdMovieListObservable;
private Repository mRepository;
private Int mMovieType = 0;
public MoviesViewModel(Application application) {
super(application);
mRepository = new Repository();
mLastdMovieListObservable = Transformations.switchMap(mMovieType, type->
mLastdMovieListObservable = mRepository.getMovieList(type)
);
}
public LiveData<Resource<List<Movie>>> getMovieListLiveData() {
return mLastdMovieListObservable;
}
public void setMovieType(Int movieType){
mMovieType.setValue(movieType)
}
}
Related
I'm dealing with something that I don't understand at all.
If I delete the observer before updating my Room database and then put the observer back on, I have notifications for each update, and the recyclerview is updated as many times.
My partial code:
public class ArticlesListFragment extends Fragment {
private LiveData<List<Article>> mLDgetAllArticle;
private long mClistId;
private ArrayList<Article> mArticles;
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
this.mLDgetAllArticle = this.articleViewModel.getAllArticle(mclistId);
this.setArticlesObserver();
...
}
private void setArticlesObserver() {
if (this.mLDgetAllArticle != null && !this.mLDgetAllArticle.hasObservers())
this.mLDgetAllArticle.observe(getViewLifecycleOwner(), this::updateArticlesList);
}
private void updateArticlesList(List<Article> articles) {
this.mArticles = new ArrayList<>(articles);
this.mArticlesRecyclerViewAdapter.setAdapterDatas(this.mArticles);
}
private void removeArticlesObserver() {
if (this.mLDgetAllArticle != null && this.mLDgetAllArticle.hasObservers())
this.mLDgetAllArticle.removeObservers(getViewLifecycleOwner());
}
private void updateArticle(Article article) {
this.articleViewModel.updateArticle(article);
}
...
}
Everything is fine so far.
But, elsewhere, I have to update all my Articles, like:
for (int i = 0; i < this.mArticles.size(); i++) {
this.mArticles.get(i).setOrd(i);
this.updateArticle(this.mArticles.get(i));
}
Also, I thought I should delete the observer before, and put it back later:
this.removeArticlesObserver();
for (int i = 0; i < this.mArticles.size(); i++) {
this.mArticles.get(i).setOrd(i);
this.updateArticle(this.mArticles.get(i));
}
this.setArticlesObserver();
but I still get after all the updates notifications. updateArticlesList is called as many times as there were updateArticles, after setArticlesObserver.
What am I missing?
Is there any way to flush all that waiting results before setting observer again?
ViewModelFactory.java:
public class ViewModelFactory implements ViewModelProvider.Factory {
private final ArticleDataRepository articleDataSource;
private final Executor executor;
private static volatile ViewModelFactory factory;
public static ViewModelFactory getInstance(Context context) {
if (factory == null) {
synchronized (ViewModelFactory.class) {
if (factory == null) {
factory = new ViewModelFactory(context);
}
}
}
return factory;
}
private ViewModelFactory(Context context) {
getDB database = getDB.getInstance(context);
this.articleDataSource = new ArticleDataRepository(database.articleDao());
this.executor = Executors.newSingleThreadExecutor();
}
#Override
#NotNull
public <T extends ViewModel> T create(Class<T> modelClass) {
if (modelClass.isAssignableFrom(ArticleViewModel.class)) {
return (T) new ArticleViewModel(articleDataSource, executor);
}
throw new IllegalArgumentException("Unknown ViewModel class");
}
}
ArticleViewModel.java:
public class ArticleViewModel extends ViewModel {
private final ArticleDataRepository articleDataSource;
private final Executor executor;
public ArticleViewModel(
ArticleDataRepository articleDataSource,
Executor executor
) {
this.articleDataSource = articleDataSource;
this.executor = executor;
}
public LiveData<List<Article>> getAllArticle(long clistId) {
return articleDataSource.getAll(clistId);
}
}
ArticleDataRepository:
public class ArticleDataRepository {
private final ArticleDao articleDao;
public ArticleDataRepository(ArticleDao articleDao) {
this.articleDao = articleDao;
}
public LiveData<List<Article>> getAll(long clistId) {
return this.articleDao.getAll(clistId);
}
}
I used this documentation
I took two steps to solve my problem:
I'm updating all the articles instead of doing a part one I didn't know I could do
#Update(onConflict = OnConflictStrategy.REPLACE)
void updateAll(ArrayList articles);
for (int i = 0; i < this.mArticles.size(); i++) {
this.mArticles.get(i).setOrd(i);
this.updateArticle(this.mArticles.get(i));
}
becomes:
for (int i = 0; i < this.mArticles.size(); i++) {
this.mArticles.get(i).setOrd(i);
}
this.updateAllArticles(this.mArticles);
I put a flag to launch the notifyDataSetChanged() only during specific events and not each time the data is updated.
I've tried using the below code to load some data from my database and display them in an activity:
PhoneNumberViewModel phoneNumberViewModel =
new ViewModelProvider(WorksideContactCard.this).get(PhoneNumberViewModel.class);
phoneNumberViewModel
.getPhoneNumbersById(contactID)
.observe(this,
numbers -> {
phoneNumberList = numbers;
});
To be precise, I needn't observe the data changing, as the data will be refreshed if the activity is resumed. But the above code freezes my application, although I am accessing the DB using an AsyncTask in my other class.
Why could this be so?
EDIT:
PhoneNumberViewModel.class:
public class PhoneNumberViewModel extends AndroidViewModel {
private PhoneNumberRepository phoneNumberRepository;
private LiveData<List<PhoneNumber>> allPhoneNumbers;
public PhoneNumberViewModel(#NonNull Application application) {
super(application);
phoneNumberRepository = new PhoneNumberRepository(application);
allPhoneNumbers = phoneNumberRepository.getAllPhoneNumbersLive();
}
public void insert(PhoneNumber phoneNumber) {
System.out.println("[PhoneNumberRepository] Adding new phoneNumber");
phoneNumberRepository.insert(phoneNumber);
}
public void update(PhoneNumber phoneNumber) {
phoneNumberRepository.update(phoneNumber);
}
public void delete(PhoneNumber phoneNumber) {
phoneNumberRepository.delete(phoneNumber);
}
public LiveData<List<PhoneNumber>> getAllPhoneNumbers() {
return allPhoneNumbers;
}
public LiveData<List<PhoneNumber>> getPhoneNumbersById(long contactId)
throws ExecutionException, InterruptedException {
return phoneNumberRepository.getPhoneNumbersByContactId(contactId);
}
PhoneNumberRepository.class
public class PhoneNumberRepository {
private PhoneNumberDao phoneNumberDao;
private LiveData<List<PhoneNumber>> allPhoneNumbers;
private LiveData<List<PhoneNumber>> phoneNumbersByIdList;
public PhoneNumberRepository(Application application) {
WorksideDatabase database = WorksideDatabase.getInstance(application);
phoneNumberDao = database.phoneNumberDao();
allPhoneNumbers = phoneNumberDao.getAllPhoneNumbers();
}
...
public LiveData<List<PhoneNumber>> getAllPhoneNumbersLive() {
return allPhoneNumbers;
}
public void deleteAllPhoneNumbers() {
new DeleteAllPhoneNumbersAsyncTask(phoneNumberDao).execute();
}
public LiveData<List<PhoneNumber>> getPhoneNumbersByContactId(long id)
throws ExecutionException, InterruptedException {
return new SelectPhoneNumberByIdAsyncTask(phoneNumberDao, id).get();
}
private static class SelectPhoneNumberByIdAsyncTask
extends AsyncTask<Long, Void, LiveData<List<PhoneNumber>>> {
private PhoneNumberDao phoneNumberDao;
private Long ID;
private SelectPhoneNumberByIdAsyncTask(PhoneNumberDao phoneNumberDao, Long contactId) {
this.phoneNumberDao = phoneNumberDao;
ID = contactId;
}
#Override
protected LiveData<List<PhoneNumber>> doInBackground(Long... contactId) {
ID = contactId[0];
return phoneNumberDao.getPhoneNumbersById(ID);
}
}
...
}
PhoneNumberDao.class:
#Dao
public interface PhoneNumberDao {
#Insert
void insert(PhoneNumber phoneNumber);
#Update
void update(PhoneNumber phoneNumber);
#Delete
void delete(PhoneNumber phoneNumber);
// Probably uneeded method
#Query("DELETE FROM phone_numbers_table")
void deleteAllPhoneNumbers();
// Delete entry/entries by ID
#Query("DELETE FROM phone_numbers_table WHERE id = :phoneNumberId")
void deleteByPhoneNumberId(long phoneNumberId);
// Retrieve entry/entries by contact ID
// #Query("SELECT * FROM phone_numbers_table WHERE contact_id = :contactId")
// List<PhoneNumber> getPhoneNumbersById(long contactId);
// Retrieve all saved phone numbers in LiveData format
#Query("SELECT * FROM phone_numbers_table")
LiveData<List<PhoneNumber>> getAllPhoneNumbers();
#Query("SELECT * FROM phone_numbers_table WHERE contact_id = :contactId")
LiveData<List<PhoneNumber>> getPhoneNumbersById(long contactId);
#Query("SELECT * FROM phone_numbers_table")
List<PhoneNumber> getAll();
}
return new SelectPhoneNumberByIdAsyncTask(phoneNumberDao, id).get();
this here is your problem ,.get() call blocks the main thread
I would suggest switching to Kotlin and using Coroutines or here you can handle this using callbacks and not using AsyncTask.get() which blocks the main thread.Also BTW Async Task is going to be deprecated last I heard ,So keep that in mind too.
This is one solution the problem
public class Repository implements
SelectPhoneNumberByIdAsyncTask.OnPhoneNumberFound {
#Override
public void onPhoneNumberFound(LiveData<List<Object>> list) {
//you can get the data you want here
}
}
class SelectPhoneNumberByIdAsyncTask extends AsyncTask<Long, Void,
LiveData<List<PhoneNumber//Correct this>>> {
interface OnPhoneNumberFound{
void onPhoneNumberFound(LiveData<List<PhoneNumberDao>> list);
}
OnPhoneNumberFound mListener;
private PhoneNumberDao phoneNumberDao;
private Long ID;
private SelectPhoneNumberByIdAsyncTask(Object phoneNumberDao, Long contactId) {
this.phoneNumberDao = phoneNumberDao;
ID = contactId;
}
#Override
protected LiveData<List<Object>> doInBackground(Long... contactId) {
ID = contactId[0];
mListener.onPhoneNumberFound(phoneNumberDao.getPhoneNumbersById(ID));
return null;
}
}
use .execute() call now instead of .get()
I am at my last year at the university and working on my final project with a group of friends.
I am responsible on implementing the database (using google firestore in java) and i am trying to implement it using a design pattern.
I found the adapter quiet useful, as I can create an interface called:
GenericDB, which contains all the methods the database needs to use.
A concrete class, let's call her FirestoreDB which implements it,
and an Adapter, which also implements the GenericDB, and holds an Instance Of GenericDB as a variable, so I can choose at run time which db I will want to use (maybe in the future the db would change)
Here is some basic code:
public interface GenericDB {
boolean add(String... args);
boolean delete(String... args);
boolean get(String... args);
boolean changePassword(String... args);
}
public class FirestoreDB implements GenericDB {
private final Firestore db;
public FirestoreDB() {
FirestoreOptions firestoreOptions =
FirestoreOptions.getDefaultInstance().toBuilder()
.setProjectId(Constants.PROJECT_ID)
.build();
this.db = firestoreOptions.getService();
}
public boolean add(String... args) {
return true;
}
public boolean delete(String... args) {
return false;
}
public boolean get(String... args) {
return false;
}
public boolean changePassword(String... args) {
return false;
}
}
public class Adapter implements GenericDB {
private GenericDB db;
public Adapter(GenericDB db){
this.db = db;
}
public boolean add(String... args) {
return this.db.add(args);
}
public boolean delete(String... args) {
return db.delete(args);
}
public boolean get(String... args) {
return db.get(args);
}
public boolean changePassword(String... args) {
return db.changePassword(args);
}
}
public class DatabaseCreator {
public GenericDB getDB(DATABASE database) {
switch (database) {
case FIRESTORE:
return new FirestoreDB();
default:
return null;
}
}
DatabaseCreator database = new DatabaseCreator();
GenericDB db = database.getDB(EXTRA.DATABASE.FIRESTORE);
Adapter ad = new Adapter(db);
System.out.println(ad.add("1"));
Is this a good use of the adapter pattern?
Is this a good use of the adapter pattern?
What you call Adapter, is not an Adapter. Purpose of Adapter is to convert interface of the class to another interface expected by the client. But your 'adapter' implements the same GenericDB interface as the adaptee it wraps - no conversion happens here.
The closest one to Adapter is your FirestoreDB class. It is intended to convert the interface of Firestore to the interface GenericDB required by your code. Of course there is still small chance that Google will change Firestore to implement your GenericDB interface. Until then you should use an adapter.
To make FirestoreDB a proper adapter, you should pass adaptee (Firestore object) to the constructor of adapter, and later call it in the GenericDB methods implementation:
public class FirestoreAdapter implements GenericDB {
private final Firestore db;
public FirestoreAdapter(Firestore db) { // pass adaptee to adapter
this.db = db;
}
public boolean add(...) {
// DocumentReference docRef = db.collection(colName).document(docId);
// etc
}
// etc
}
This adapter could be passed to the code which expects GenericDB interface and knows nothing about Firestore interface:
FirestoreOptions firestoreOptions =
FirestoreOptions.getDefaultInstance().toBuilder()
.setProjectId(Constants.PROJECT_ID)
.build();
Firestore firestore = firestoreOptions.getService(); // but your code requires GenericDB
GenericDB db = new FirestoreAdapter(firestore); // adapt Firestore interface
// use db here as if Firestore was implementing your GenericDB interface
You can write adapters for another type of database in the same way. But... usually, you don't use some abstract database interface, because databases are very different with different sets of features. Trying to find some intersection of features supported by all databases might be not a great idea (unless you writing CosmosDB). Usually, you will work with higher-level abstractions, like Repositories.
You should use Command Design Pattern it more flexible than Adapter in your case
Example:
import java.util.HashMap;
import java.util.Map;
// demo for firestore
class Firestore {
private Map<Object, Object> map = new HashMap<>();
public void add(Object id, Object object) {
map.put(id, object);
}
public Object get(Object id) {
return map.get(id);
}
}
interface FirestoreAware {
void setFirestore(Firestore firestore);
}
enum CommandType {
ADD,
DELETE,
GET,
CHANGE_PASSWORD,
GET_USER
}
interface Command {
CommandType getType();
}
class GetCommand implements Command {
private int id;
public GetCommand id(int id) {
this.id = id;
return this;
}
public int getId() {
return id;
}
#Override
public CommandType getType() {
return CommandType.GET;
}
}
class AddCommand implements Command {
private int id;
private String jsonData;
public AddCommand id(int id) {
this.id = id;
return this;
}
public AddCommand jsonData(String jsonData) {
this.jsonData = jsonData;
return this;
}
public int getId() {
return id;
}
public String getJsonData() {
return jsonData;
}
#Override
public CommandType getType() {
return CommandType.ADD;
}
}
interface CommandHandler<C> {
Object handle(C cmd);
}
abstract class CommandFirestoreHandler<C>
implements CommandHandler<C>, FirestoreAware {
protected Firestore firestore;
#Override
public void setFirestore(Firestore firestore) {
this.firestore = firestore;
}
}
class AddCommandHandler extends CommandFirestoreHandler<AddCommand> {
#Override
public Object handle(AddCommand cmd) {
firestore.add(cmd.getId(), cmd.getJsonData());
return Boolean.TRUE;
}
}
class GetCommandHandler extends CommandFirestoreHandler<GetCommand> {
#Override
public Object handle(GetCommand cmd) {
return firestore.get(cmd.getId());
}
}
interface GenericDB {
<T> T execute(Command cmd);
}
class FirestoreDB implements GenericDB {
private final Firestore firestore;
private final Map<CommandType, CommandHandler> handlers;
public FirestoreDB() {
this(new Firestore());
}
public FirestoreDB(Firestore firestore) {
this.firestore = firestore;
this.handlers = new HashMap<>();
// demo add default command handlers
this.addHandler(CommandType.ADD, new AddCommandHandler());
this.addHandler(CommandType.GET, new GetCommandHandler());
}
public void addHandler(CommandType commandType, CommandHandler handler) {
if(handler instanceof FirestoreAware)
((FirestoreAware)handler).setFirestore(firestore);
this.handlers.put(commandType, handler);
}
#Override
public <T> T execute(Command cmd) {
CommandHandler handler = handlers.get(cmd.getType());
return (T)handler.handle(cmd);
}
}
class DatabaseCreator {
public GenericDB getDB(String database) {
switch (database) {
case "FIRESTORE":
return new FirestoreDB();
default:
return null;
}
}
}
public class GenericDBDemo {
public static void main(String[] args) {
DatabaseCreator database = new DatabaseCreator();
GenericDB db = database.getDB("FIRESTORE");
db.execute(new AddCommand().id(1).jsonData("{'_id': 1, 'name' : 'hello world'}"));
System.out.println(db.execute(new GetCommand().id(1)).toString());
}
}
I'm trying to write some tests for my Android app using Mockito and I keep getting this error:
Wanted but not invoked:
view.displayProfiles(
[com.example.reconheca.model.Profile#1e800aaa, com.example.reconheca.model.Profile#185a6e9, com.example.reconheca.model.Profile#6f03482]
);
-> at com.example.reconheca.view.ProfileListActivityPresenterTest.shouldPassProfileToView(ProfileListActivityPresenterTest.java:48)
Actually, there were zero interactions with this mock.
These are my classes been called:
ProfileListActivityPresenterTest
private String usedId = "string";
private ArrayList<Profile> PROFILE_LIST = new ArrayList<>(Arrays.asList(new Profile(), new Profile(), new Profile()));
private Task<ArrayList<Profile>> task;
private ProfileListActivityPresenter presenter;
#Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
#Mock
ProfileFirestoreRepository profileRepository;
#Mock
ProfileListActivityView view;
#Before
public void setUp() {
task = getTestTaskWithResult();
presenter = new ProfileListActivityPresenter(view, profileRepository);
}
#Test
public void shouldPassProfileToView() {
when(profileRepository.getProfilesFromUser(usedId)).thenReturn(task);
presenter.loadProfiles(usedId);
verify(view).displayProfiles(task.getResult());
}
#Test
public void shouldHandleNoProfile() {
when(profileRepository.getProfilesFromUser(usedId)).thenReturn(task);
presenter.loadProfiles(usedId);
verify(view, never()).displayNoProfiles();
}
public Task<ArrayList<Profile>> getTestTaskWithResult() {
TaskCompletionSource<ArrayList<Profile>> taskCompletionSource = new TaskCompletionSource<>();
taskCompletionSource.setResult(PROFILE_LIST);
return taskCompletionSource.getTask();
}
ProfileListActivityPresenter
private ProfileListActivityView view;
private ProfileFirestoreRepository profileFirestoreRepository;
public ProfileListActivityPresenter(ProfileListActivityView view, ProfileFirestoreRepository profileFirestoreRepository) {
this.view = view;
this.profileFirestoreRepository = profileFirestoreRepository;
}
public void loadProfiles(String userId) {
Task<ArrayList<Profile>> task = profileFirestoreRepository.getProfilesFromUser(userId);
task.addOnCompleteListener(new OnCompleteListener<ArrayList<Profile>>() {
#Override
public void onComplete(#NonNull Task<ArrayList<Profile>> task) {
task.addOnSuccessListener(profiles -> {
if (profiles.isEmpty()){
view.displayNoProfiles();
} else {
view.displayProfiles(profiles);
}
})
.addOnFailureListener(exception -> view.displayError(exception.getMessage()));
}
});
}
ProfileListActivity
public class ProfileListActivity extends AppCompatActivity implements ProfileListActivityView {
// Omitted code
#Override
public void displayProfiles(List<Profile> profileList) {
Profile[] profiles = profileList.toArray(new Profile[0]);
profileAdapter = new ProfileAdapter(ProfileListActivity.this, profiles);
gridView.setAdapter(profileAdapter);
progressBar.setVisibility(View.GONE);
}
#Override
public void displayNoProfiles() {
noProfileTextView.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
}
}
DatabaseProfileFirestoreRepository
public class DatabaseProfileFirestoreRepository implements ProfileFirestoreRepository {
private static final String TAG = "ProfileFirestoreRepo";
private final CollectionReference collectionReference;
private final String collectionName;
public DatabaseProfileFirestoreRepository(String collectionName) {
this.collectionName = collectionName;
FirebaseFirestore db = FirebaseFirestore.getInstance();
this.collectionReference = db.collection(this.collectionName);
}
#Override
public Task<ArrayList<Profile>> getProfilesFromUser(String userId) {
Log.i(TAG, "Getting profiles...");
return collectionReference
.whereEqualTo(Constant.PROFILE_USER_ID, userId)
.get()
.continueWith(new Continuation<QuerySnapshot, ArrayList<Profile>>() {
#Override
public ArrayList<Profile> then(#NonNull Task<QuerySnapshot> task) throws Exception {
ArrayList<Profile> profileList = new ArrayList<>();
QuerySnapshot querySnapshot = task.getResult();
assert querySnapshot != null;
if (querySnapshot.isEmpty()){
return profileList;
} else {
for (QueryDocumentSnapshot document : task.getResult()) {
Map<String, Object> data = document.getData();
Profile profile = new Profile(document.getId(), (String) data.get(Constant.PROFILE_USER_ID), (String) data.get(Constant.PROFILE_NAME), (String) data.get(Constant.PROFILE_PICTURE_PATH));
profileList.add(profile);
}
return profileList;
}
}
});
}
}
The method getProfilesFromUser return a task using Cloud Firestore located at DatabaseProfileFirestoreRepository.
After the task is completed, the onSuccess send the profiles to my Activity and the Activity displays the profiles on the screen.
Using the debug mode, I see that the code never executes the task.addOnCompleteListener and forward so displayProfiles never gets called. I can't figure out what I'm doing wrong.
Obs.: The code works well, the problem is running the tests only
I have already asked this question two times, but I'm new to stackoverflow and it seems that I don't know the rules for formatting my example code in here. Now I've decided to give the full stack of the calls and I hope I can explain the situation because everything is so strange and I can't find the words to describe it. First I will give you the source of the classes that have something to do with the problem. My actual question is in the end of the page. The large piece of code is just in case, because I don't know what could be the explanation of my problem.
Here is a service facade that gets calls from my flex application.
public class ServiceFacade implements IAuthenticationService, IProfileService, ICampaignService {
#Autowired
private IAuthenticationService authenticationService;
#Autowired
private IProfileService profileService;
#Autowired
private ICampaignService campaignService;
public void login(User user) throws AuthenticationException{
authenticationService.login(user);
}
#Override
public void logout() throws AuthenticationException {
authenticationService.logout();
}
#Override
public void sendForgottenPassword(String email) {
authenticationService.sendForgottenPassword(email);
}
#Override
public Profile getProfile(Long userId) {
return profileService.getProfile(userId);
}
#Override
public Profile updateProfile(Profile profile) {
return profileService.updateProfile(profile);
}
#Override
public Collection<String> getSocialConnectionsTypes(Long userId) {
return profileService.getSocialConnectionsTypes(userId);
}
#Override
public List<Email> findDuplicateEmails(Long profileId, List<Email> emails) {
return profileService.findDuplicateEmails(profileId, emails);
}
#Override
public Campaign getCampaign(Long campaignId) {
return campaignService.getCampaign(campaignId);
}
#Override
public Campaign updateCampaign(Campaign campaign) {
return campaignService.updateCampaign(campaign);
}
#Override
public void removeCampaign(Long campaignId) {
campaignService.removeCampaign(campaignId);
}
#Override
public void setPools(Long campaignId, Collection<Pool> pools) {
campaignService.setPools(campaignId, pools);
}
#Override
public void addPool(Long campaignId, Pool pool) {
campaignService.addPool(campaignId, pool);
}
#Override
public void removePool(Long campaignId, Pool pool) {
campaignService.removePool(campaignId, pool);
}
#Override
public List<Campaign> getCampaigns() {
return campaignService.getCampaigns();
}
#Override
public void updatePool(Long campaignId, Pool pool) {
campaignService.updatePool(campaignId, pool);
}
}
The method which is important for my question is the findDuplicateEmails method.
The profileService is implemented in the following class:
public class ProfileService implements IProfileService {
#Autowired
private IProfileManager profileManager;
#Override
public Profile getProfile(Long userId) {
return profileManager.getProfile(userId);
}
#Override
public Profile updateProfile(Profile profile){
profileManager.updateProfile(profile);
return profile;
}
#Override
public Collection<String> getSocialConnectionsTypes(Long userId) {
return profileManager.getSocialConnectionsTypes(userId);
}
#Override
public List<Email> findDuplicateEmails(Long profileId, List<Email> emails) {
return profileManager.findDuplicateEmails(profileId, emails);
}
}
Again the important method is findDuplicateEmails
The implementation of the profileManager is the following class:
public class ProfileManager implements IProfileManager {
#Autowired
private IProfileDao profileDao;
#Autowired
private ISectionManager autoCompleteManager;
#Autowired
private IUserSecurityService userSecurityService;
#Transactional
public Profile getProfile(Long userId) {
return profileDao.getProfileByUser(userId);
}
#Transactional
public void updateProfile(final Profile profile) {
List<Major> notApprovedMajors = extractNotApprovedMajors(profile);
List<Degree> notApprovedDegrees = extractNotApprovedDegrees(profile);
List<School> notApprovedSchools = extractNotApprovedSchools(profile);
List<Language> notApprovedLanguages = extractNotApprovedLanguages(profile);
List<Position> notApprovedPositions = extractNotApprovedPositions(profile);
List<Company> notApprovedCompanies = extractNotApprovedCompanies(profile);
List<Country> notApprovedCountries = extractNotApprovedCountries(profile);
List<City> notApprovedCities = extractNotApprovedCities(profile);
List<Certificate> notApprovedCertificates = extractNotApprovedCertificates(profile);
autoCompleteManager.updateAll(notApprovedMajors);
autoCompleteManager.updateAll(notApprovedDegrees);
autoCompleteManager.updateAll(notApprovedSchools);
autoCompleteManager.updateAll(notApprovedLanguages);
autoCompleteManager.updateAll(notApprovedPositions);
autoCompleteManager.updateAll(notApprovedCompanies);
autoCompleteManager.updateAll(notApprovedCountries);
autoCompleteManager.updateAll(notApprovedCities);
autoCompleteManager.updateAll(notApprovedCertificates);
profileDao.updateProfile(profile);
}
#Override
public List<Email> findDuplicateEmails(Long profileId, List<Email> emails) {
Profile persistedProfile = profileDao.findById(profileId);
if (persistedProfile.getContact() == null)
{
persistedProfile.setContact(new Contact());
}
List<Email> resultEmails = new ArrayList<Email>();
for (int i = 0; i < emails.size(); i++) {
if ((!userSecurityService.guaranteeUniquePrincipal(emails.get(i)) &&
!isPersistedInThePersistentCollection(emails.get(i), persistedProfile.getContact().getEmails())) ||
isDuplicateInTheCurrentCollection(emails.get(i), emails, i + 1)) {
resultEmails.add(emails.get(i));
}
}
return resultEmails;
}
private boolean isDuplicateInTheCurrentCollection(Email emailToCheck, List<Email> emails, int index)
{
for (int i = index ; i < emails.size(); i ++) {
if (emails.get(i).getEmailAddress().equals(emailToCheck.getEmailAddress())) {
return true;
}
}
return false;
}
private boolean isPersistedInThePersistentCollection(Email emailToCheck, Collection<Email> emails)
{
if (emails == null) {
return false;
}
for (Email persistedEmail : emails) {
if (persistedEmail.getEmailAddress().equalsIgnoreCase(emailToCheck.getEmailAddress())) {
return true;
}
}
return false;
}
}
Again the important method is the method findDuplicateEmails
Now, after this short background, here is my problem:
I am using Hibernate with spring's HibernateTemplate. I found out that in the method findDuplicateEmails, some completely new entities which come form the flex application gets saved automatically. This was very strange and during the debbugging I found out that even if I change the method findDuplicateEmails in the ProfileManager so it looks like:
#Override
public List<Email> findDuplicateEmails(Long profileId, List<Email> emails) {
Email email = new Email();
return null;
}
the entity email gets saved automatically. I also found out that if the identifier of the entity is not "email", but something else, like "newEmail", or "email1", or something, there is no problem and the entity gets persisted if and only if I make it persistent. This problem exists only in this class and finally, this problem shows up only for the Email. I mean that if I have Phone phone = new Phone(); the entity phone gets persisted only when I wish.
The flex application first checks that the entered from the user emails are unique, and then after some user interaction calls the method updateProfile() if the entered data is valid.
I would download Hibernate sources and start debugging, you will either find a bug in Hibernate (happens) or in your code, as this is one weird behavior.
This is an advice I got once, and was the fastest, most educating way to get to the root.