MVVM и Firebase.Чтение данных из базы данных Firebase в реальном времени, что приводит к их дублированию - PullRequest
0 голосов
/ 27 сентября 2019

Я создал приложение Android с использованием облачных сервисов Java и Firebase. Цель моего приложения - позволить пользователям просматривать самые популярные или самые популярные фильмы и отмечать их как избранные.

Вот моя структура базы данных , как только пользователь нажимает кнопку «Избранное», детали фильма сохраняются в БД в реальном времени.

При первом запуске приложения фильмСписок выглядит великолепно Основное задание

, но при выходе из задания и возврате полученные фильмы будут дубликаты, подобные этому: дубликаты фильмов

Вот класс Query, который будет содержать OnActive (), где вставлен ChildEventListener, и OnInactive, где он будет удален

public class FirebaseQueryLiveData extends LiveData<DataSnapshot> {

    private final Query query;
    private final MyValueEventListener listener = new MyValueEventListener();

    public FirebaseQueryLiveData(Query query) {
        this.query = query;
    }

    public FirebaseQueryLiveData(DatabaseReference ref) {
        this.query = ref;
    }

    @Override
    protected void onActive() {
        query.addChildEventListener(listener);
    }

    @Override
    protected void onInactive() {
        if (query != null && listener != null) {
            query.removeEventListener(listener);
        }
    }

    private class MyValueEventListener implements ChildEventListener {

        @Override
        public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
            setValue(dataSnapshot);
        }

        @Override
        public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
        }

        @Override
        public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) {
        }

        @Override
        public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {
        }
    }
}

, и это мой MovieViewModel

public class MovieViewModel extends ViewModel {

    MutableLiveData<List<Movie>> movieLiveData = new MutableLiveData<>();
    FirebaseDatabase database = FirebaseDatabase.getInstance();
    DatabaseReference movieRef;
    private FirebaseQueryLiveData liveData;

    public MovieViewModel(String userName) {
        this.movieRef = database.getReference().child(userName);
        this.liveData = new FirebaseQueryLiveData(movieRef);
    }

    @NonNull
    public LiveData<DataSnapshot> getLiveData() {
        return liveData;
    }
}

этоосновной Activity, и, как вы можете видеть ниже, метод generateMovies (String mUserName) отвечает за вставку извлеченных фильмов из класса viewModel в список, и он генерируется в OnCreate, если username! = null, и OnRestart.

  public class MainActivity extends AppCompatActivity{
    private List<Movie> movies;
    private MovieAdapter adapter;
    public static final String TAG = MainActivity.class.getName();
    public static final String LIFE_CYCLE = "LIFE CYCLE";
    private FirebaseAuth mFirebaseAuth;
    private FirebaseAuth.AuthStateListener mAuthStateListener;
    public static final int RC_SIGN_IN = 1;
    public static final String ANONYMOUS = "anonymous";

    private String mUserName;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        Log.d(LIFE_CYCLE,"onCreate");

        mFirebaseAuth = FirebaseAuth.getInstance();

        mAuthStateListener = new FirebaseAuth.AuthStateListener() {
            @Override
            public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
                FirebaseUser user = firebaseAuth.getCurrentUser();
                if (user != null){
                    //user is signed in
                    Log.d(TAG, "onAuthStateChanged: " +user.getDisplayName());
                    mUserName = user.getDisplayName();

                    //if user is signed in generate Movies and pass in the username
                    generateMovies(mUserName);
                }else  {
                    onSignedOutCleanUp();
                    //user is signed out
                    startActivityForResult(
                            AuthUI.getInstance()
                                    .createSignInIntentBuilder()
                                    .setIsSmartLockEnabled(false)
                                    .setAvailableProviders(Arrays.asList(
                                          new AuthUI.IdpConfig.GoogleBuilder().build(),
                                          new AuthUI.IdpConfig.EmailBuilder().build()))
                                    .build(), RC_SIGN_IN);
                }
            }
        };
        //when the device is rotated the adapter will be null so we have to init in onCreate
        adapter = new MovieAdapter(getApplicationContext(),movies,mUserName);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == RC_SIGN_IN){
             if (resultCode == RESULT_OK){
                 Toast.makeText(MainActivity.this,"Signed In!",Toast.LENGTH_SHORT).show();
             } else if (resultCode == RESULT_CANCELED){
                 Toast.makeText(MainActivity.this,"Signed In  canceled!!",Toast.LENGTH_SHORT).show();
                 finish(); }
        }
    }



    @Override
    protected void onResume() {
        super.onResume();
        mFirebaseAuth.addAuthStateListener(mAuthStateListener);
         if (movies != null){
             movies.clear();
             Log.d(LIFE_CYCLE, "Clear ");
         }
        Log.d(LIFE_CYCLE,"onResume");


    }

    @Override
    protected void onPause() {
        super.onPause();

        mFirebaseAuth.removeAuthStateListener(mAuthStateListener);

        Log.d(LIFE_CYCLE, "onPause");
        if (movies != null){
            movies.clear();
            Log.d(LIFE_CYCLE, "Clear");
        }

    }

    @Override
    protected void onStart() {
        super.onStart();

        Log.d(LIFE_CYCLE, "onStart");
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater menuInflater = getMenuInflater();
        menuInflater.inflate(R.menu.menu_sorting, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.settings:
                Intent intent = new Intent(this, Settings.class);
                startActivity(intent);
                return true;

            case R.id.sign_out_menu:
                AuthUI.getInstance().signOut(this);
        }
        return super.onOptionsItemSelected(item);

    }

    private String getSortValue() {
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        Log.d("MainActivity","the preference is "+sharedPreferences.getString(getString(R.string.sort_by_key), getString(R.string.sort_popularity)));
        return sharedPreferences.getString(getString(R.string.sort_by_key), getString(R.string.sort_popularity));
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(LIFE_CYCLE, "onRestart ");
        Toast.makeText(this, "Sorting by   " + getSortValue(),Toast.LENGTH_LONG).show();
        movies.clear();
        generateMovies(mUserName);
    }

    private void onSignedOutCleanUp(){
        mUserName = ANONYMOUS;
        if (movies != null){
            movies.clear();
            adapter.notifyDataSetChanged();
        }


    }

    private void generateMovies(String mUserName){
        Log.d(LIFE_CYCLE,"GENERATE ");
        movies = new ArrayList<>();
        ViewModelFactory modelFactory = new ViewModelFactory(mUserName);
        MovieViewModel viewModel = ViewModelProviders.of(this,modelFactory).get(MovieViewModel.class);
        if (getSortValue().equals(getString(R.string.sort_by_favorites))) {
          movies.clear();
          adapter.notifyDataSetChanged();

          viewModel.getLiveData().observe(this, new Observer<DataSnapshot>() {

              @Override
              public void onChanged(DataSnapshot dataSnapshot) {

                  if (dataSnapshot != null) {

                      for (DataSnapshot itemSnapShot : dataSnapshot.getChildren()) {
                        Movie movie = itemSnapShot.getValue(Movie.class);
                          Log.d(TAG, "itemSnapShot = " + itemSnapShot.getValue().toString());

                          movies.add(movie);

                         adapter.notifyDataSetChanged();

                      }
                      Log.d(TAG, "movies list: "+movies.size());

                  }
              }
          });

        } else if (getSortValue().equals(getString(R.string.sort_popularity))){
            viewModel.getMovies( getString(R.string.sort_popularity) ).observe(this, new Observer<List<Movie>>() {
                @Override
                public void onChanged(@Nullable List<Movie> moviesModels) {
                    movies.clear();
                    if (moviesModels != null) {
                        movies.addAll(moviesModels);
                        adapter.notifyDataSetChanged();
                    }
                }
            }); }else if (getSortValue().equals(getString(R.string.sort_top_rated))){
            viewModel.getMovies(getString(R.string.sort_top_rated)).observe(this, new Observer<List<Movie>>() {
                @Override
                public void onChanged(@Nullable List<Movie> moviesModels) {
                    movies.clear();
                    if (moviesModels != null) {
                        movies.addAll(moviesModels);
                        adapter.notifyDataSetChanged();
                    }
                }
            });
        }

        Toast.makeText(this, "Sorting by   " + getSortValue(),Toast.LENGTH_LONG).show();

        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        // Calling the Adapter object and setting it to the recycler view.
        adapter = new MovieAdapter(this, movies,mUserName);

        recyclerView.setAdapter(adapter);
        GridLayoutManager layoutManager = new GridLayoutManager(this, 3);
        recyclerView.setLayoutManager(layoutManager);

    }

}



...