معماری MVVM با تزریق وابستگی Hilt, RxJava3, Live Dataو View Binding در اندروید

معماری MVVM با تزریق وابستگی Hilt, RxJava3, Live Dataو View Binding در اندروید
فهرست مقاله [نمایش]

    برای دانلود سورس اینجا کلیک کنید

    در این مقاله ، نحوه اجرای معماری MVVM با Hilt ، RxJava ، Retrofit ، Room ، Live Data و View Binding را خواهیم دید.

    این مقاله برای شخصی است که میخواهد

    • تزریق وابستگی جدید با استفاده از Hilt
    • مهاجرت به Hilt از داگر

    را یاد بگیرد.

    در این پروژه ، جزئیات که یک API REST تشکیل می دهد را به دست می آوریم ،و سپس از Room برای ذخیره آن به صورت آفلاین استفاده می کنیم. از آنجا که تعدادی مفاهیم در اینجا مورد استفاده قرار می گیرند، به تک تک آنها خواهیم پرداخت.

    ساختار پروژه:

    ابتدا تمام وابستگی های لازم را به پروژه خود اضافه کنید.

    apply plugin: 'com.android.application'
    apply plugin: 'dagger.hilt.android.plugin'
    
    
    android {
        compileSdkVersion 29
        buildToolsVersion "29.0.2"
    
        defaultConfig {
            applicationId "com.example.pokemon"
            minSdkVersion 19
            targetSdkVersion 29
            versionCode 1
            versionName "1.0"
            multiDexEnabled true
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        compileOptions {
            sourceCompatibility = 1.8
            targetCompatibility = 1.8
        }
    
        viewBinding {
            enabled = true
        }
    
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
    
        def room_version = "2.2.5"
        def nav_version = "2.3.0-beta01"
    
        implementation "androidx.recyclerview:recyclerview:1.1.0"
        implementation "androidx.cardview:cardview:1.0.0"
        implementation 'com.google.android.material:material:1.1.0'
        implementation 'com.android.support:multidex:1.0.3'
    
        // Hilt
        implementation "com.google.dagger:hilt-android:2.28-alpha"
        annotationProcessor 'com.google.dagger:hilt-android-compiler:2.28-alpha'
        implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
        annotationProcessor 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
    
        //RxJava
        implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
        implementation 'io.reactivex.rxjava3:rxjava:3.0.0'
    
        // Retrofit
        implementation 'com.squareup.retrofit2:retrofit:2.9.0'
        implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
        implementation "com.github.akarnokd:rxjava3-retrofit-adapter:3.0.0"
    
        // ViewModel
        implementation 'androidx.lifecycle:lifecycle-viewmodel:2.2.0'
    
        // LiveData
        implementation 'androidx.lifecycle:lifecycle-livedata:2.2.0'
    
        // Room
        implementation "androidx.room:room-runtime:$room_version"
        annotationProcessor "androidx.room:room-compiler:$room_version"
        implementation "androidx.room:room-rxjava2:$room_version"
    
        // Navigation
        implementation "androidx.navigation:navigation-fragment:$nav_version"
        implementation "androidx.navigation:navigation-ui:$nav_version"
    
        // Glide
        implementation 'com.github.bumptech.glide:glide:4.11.0'
        annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
    
    
        implementation 'androidx.appcompat:appcompat:1.1.0'
        implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'androidx.test.ext:junit:1.1.1'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    }
     
    

    تنظیم Api Service

    برای واکشی جزئیات در اینجا از PokeApi استفاده می کنیم.

    public interface PokeApiService {
    
        @GET("pokemon")
        Observable<PokemonResponse> getPokemons();
    }
    

    همانطور که می بینید نوع بازگشت Observable است ،  در مورد RxJava اطلاعات بعدی را بعداً در مقاله مشاهده خواهید کرد.

    کلاس مدل Pokemon و PokemonResponse ما به این شکل است:

    public class Pokemon {
    
        private int id;
        private String name;
    
        private String url;
    
        public Pokemon(String name, String url) {
            this.name = name;
            this.url = url;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    }
    

     

     

    public class PokemonResponse {
        private Integer count;
        private String next,previous;
        private ArrayList<Pokemon> results;
    
        public PokemonResponse(Integer count, String next, String previous, ArrayList<Pokemon> results) {
            this.count = count;
            this.next = next;
            this.previous = previous;
            this.results = results;
        }
    
        public Integer getCount() {
            return count;
        }
    
        public void setCount(Integer count) {
            this.count = count;
        }
    
        public String getNext() {
            return next;
        }
    
        public void setNext(String next) {
            this.next = next;
        }
    
        public String getPrevious() {
            return previous;
        }
    
        public void setPrevious(String previous) {
            this.previous = previous;
        }
    
        public ArrayList<Pokemon> getResults() {
            return results;
        }
    
        public void setResults(ArrayList<Pokemon> results) {
            this.results = results;
        }
    }
    

     

    تنظیم Hilt

    کلاس BaseApplication این کلاس برای Hilt لازم است و باید انوتیشین @HiltAndroidApp را برای آن بنویسیم فراموش نکنید که این کلاس را به manifest اضافه کنید

    <application
        android:name=".BaseApplication"
    
    
    package com.example.pokemon;
    
    import android.app.Application;
    
    import dagger.hilt.android.HiltAndroidApp;
    
    
    @HiltAndroidApp
    public class BaseApplication extends Application {
    }
    

     

    اکنون ماژول Network را ایجاد خواهیم کرد.

    حالا ماژول چیست؟

    ماژول کلاسی است که اطلاعاتی را در مورد چگونگی تهیه instance از کلاس که ما از آن برخوردار نیستیم ، در اختیارمان قرار می دهد. برای اینکه بتوانیم در Hilt از اشیاء استفاده کنیم  از انوتیشن Inject@ بالاتر از سازنده کلاس استفاده می کنیم اما هنگامی که ما یک کلاس نداریم یا وقتی با یک interface کار می کنیم که سازنده ندارد ، کجا می توانیم انوتیشن @inject را قرار دهیم. در این موارد ما از انوتیشن Provide@ در داخل کلاس ماژول استفاده می کنیم تا به hilt بگوییم که این object  ها را برای ما بسازد.

    برای راه اندازی ماژول یک کلاس NetworkModule و حاشیه نویسی آن با انوتیشن Module@ اکنون باید یک انوتیشن  دیگر اضافه کنیم که انوتیشن InsatallIn@ است و ما ApplicationComponent  را به آن پاس میدهیم زیرا ما می خواهیم NetworkModule برای application scope در دسترس ما باشد.در ماژول متدی را برای دریافت شیء PokeApiService ارائه خواهیم داد. یک متد PokeApiService از نوع بازگشت PokeApiService ایجاد کنید و آن را با انوتیشن Provide@ بنویسید.

    @Module
    @InstallIn(ApplicationComponent.class)
    public class NetworkModule {
    
        @Provides
        @Singleton
        public static PokeApiService providePokemonApiService(){
    
            return  new Retrofit.Builder()
                    .baseUrl(" https://pokeapi.co/api/v2/")
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
                    .build()
                    .create(PokeApiService.class);
        }
    }
    

    برنامه ما دو fragment  دارد که یکی از آنها لیست pokemon گرفته شده از API را نشان میدهد و fragment دوم pokemon مورد علاقه خود را که با استفاده از Room ذخیره کرده ایم نشان می دهد و ما از دکمه ای برای جابجایی بین این fragment ها استفاده خواهیم کرد.

     

    تنظیماتRepository

     

    public class Repository {
    
        private PokeDao pokeDao;
        private PokeApiService apiService;
    
        @Inject
        public Repository(PokeDao pokeDao, PokeApiService apiService) {
            this.pokeDao = pokeDao;
            this.apiService = apiService;
        }
    
    
        public Observable<PokemonResponse> getPokemons(){
            return apiService.getPokemons();
        }
    }

    ما از Inject@ در بالای سازنده Repository استفاده کرده ایم تا هر زمان که به یک شی Repository احتیاج داشته باشیم ، یک Repository را برای ما فراهم می کند. کلاس Repository دارای دو وابستگی PokeApiService و PokeDao است.

    PokeDao مربوط به Room است که آن را نادیده می گیرد و اکنون در مقاله در مورد آن صحبت خواهیم کرد.

    نکته مهم این است که اگر ما می خواهیم hilt به ما Repository را ارائه دهد ، ما همچنین باید بگوییم که چگونه نمونه ای از کلاس ها یا interface هایی را که کلاس Repository ما به آنها وابستگی دارد ارائه دهیم. ما قبلاً NetworkModule را ایجاد کرده ایم که متدی را برای گرفتن یک شیء PokeApiService ارائه می دهد. بنابر این هنگامی که ما Repository را در اختیار داریم Hilt  به صورت خودکار تمام وابستگی های کلاس Repository را تامین میکند. ما به سادگی یک متد getPokemons ایجاد کرده ایم تا Observable از نوع PokemonResponse را بازگردانیم.

     

    تنظیمات ViewModel

     

    public class PokemonViewModel extends ViewModel {
        private static final String TAG = "PokemonViewModel";
    
        private Repository repository;
        private MutableLiveData<ArrayList<Pokemon>> pokemonList = new MutableLiveData<>();
        private LiveData<List<Pokemon>> favoritePokemonList = null;
    
        @ViewModelInject
        public PokemonViewModel(Repository repository) {
            this.repository = repository;
            favoritePokemonList = repository.getFavoritePokemon();
        }
    
        public MutableLiveData<ArrayList<Pokemon>> getPokemonList() {
            return pokemonList;
        }
    
        public void getPokemons(){
            repository.getPokemons()
                    .subscribeOn(Schedulers.io())
                    .map(new Function<PokemonResponse, ArrayList<Pokemon>>() {
                        @Override
                        public ArrayList<Pokemon> apply(PokemonResponse pokemonResponse) throws Throwable {
                            ArrayList<Pokemon> list = pokemonResponse.getResults();
                            for(Pokemon pokemon : list){
                                String url = pokemon.getUrl();
                                String[] pokemonIndex = url.split("/");
                                pokemon.setUrl("https://pokeres.bastionbot.org/images/pokemon/"+pokemonIndex[pokemonIndex.length-1] +".png");
                            }
                            Log.e(TAG, "apply: "+list.get(2).getUrl());
                            return list;
                        }
                    })
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(result -> pokemonList.setValue(result),
                            error-> Log.e(TAG, "getPokemons: " + error.getMessage() ));
        }
    }
    

    در اینجا ما انوتیشن جدید ViewModelInject را مشاهده می کنیم و به من اعتماد کنید بهترین کار در مورد استفاده از Hilt است. اگر در پروژه های خود با MVVM از Dagger استفاده کرده اید ، باید با مشکل تزریق ViewModel ها روبرو شده باشید. معروف ترین راه حل ایجاد کلاس ViewModelFactory بود اما این کار طولانی و خسته کننده بود. اما با Hilt ، انوتیشن تمام کارها را انجام می دهد و به راحتی می توانید ViewModels را تزریق کنید. در اینجا ما یک متد getPokemons ایجاد کرده ایم. همانطور که می دانیم repository.getPokemons () یک Observable را برمی گرداند ما آن را observe می کنیم و سپس pokemonList (Mutable Live Data) را تنظیم می کنیم تا fragment تغییرات را به روز کند.

    RxJava دارای سه مفهوم اساسی Observable ، Observers و Operators است.

    observable به عنوانentity برای برخی از داده های منتشر شده قابل مشاهده است اما اگر کسی در observable  آن نباشد ، چه فایده ای دارد از این رو observe کنید ، بنابراین ما Observer هایی داریم که داده های منتشر شده توسط Observable ها را مشاهده می کنند. اپراتورها چیزی است که داده ها را دستکاری می کند و یا داده ها را دگرگون می کند و آن را به subscribers ها می دهد.

    SubscribOn (Schedulers.io ()) به این معنی است که می خواهیم در thread بکگراند subscribe شویم و observeOn(AndroidSchedulers.mainThread()) به این معنی است که می خواهیم داده ها را در thread اصلی مشاهده کنیم.

    در اینجا از اپراتور map استفاده می شود ، زیرا ما می خواهیم Pokemon List را دریافت کنیم ، اما ما یک Observable از PokemonReosponse را دریافت می کنیم تا بتوانیم لیست pokemon را از  pokemonResponse.getResults() بگیریم.

    اما ما همچنین می خواهیم یک چیز دیگر را تغییر دهیم. URL ای که از Pokemon Object دریافت می کنیم حاوی جزئیاتی درباره pokemon است اما ما فقط تصویر pokemon را می خواهیم بنابراین نمایه pokemon را از این URL دریافت می کنیم و سپس آن را با URL های مختلف به هم می زنیم که مستقیماً آدرس تصویر pokemon را در اختیار ما قرار می دهد.

    تنظیمات Room

    برای تنظیم Room ، ما به سه چیز Entity ، Dao و Database نیاز داریم.

    برای Entity ، کلاس Pokemon Pojo را مانند زیر به روز خواهیم کرد

    @Entity(tableName = "favorite_table")
    public class Pokemon {
    
        @PrimaryKey(autoGenerate = true)
        private int id;
        private String name;
    
        private String url;
    
        public Pokemon(String name, String url) {
            this.name = name;
            this.url = url;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    }
    

     

    برای Dao ، ما یک Interface به اسم PokeDao به شرح زیر ایجاد خواهیم کرد:

     

    @Dao
    public interface PokeDao {
    
        @Insert
        void insertPokemon(Pokemon pokemon);
    
        @Query("DELETE FROM favorite_table WHERE name = :pokemonName")
        void deletePokemon(String pokemonName);
    
        @Query("DELETE FROM favorite_table")
        void deleteAll();
    
        @Query("SELECT * FROM favorite_table")
        LiveData<List<Pokemon>> getFavoritePokemons();
    }
    

    ما متد هایی را برای قرار دادن Pokemon در لیست علاقه مندی های ما ایجاد کرده ایم ، pokemon را از لیست حذف کرده و همه پوکمون ها را از لیست دریافت می کنیم

    اکنون ما برای ذخیره pokemon مورد علاقه هایمان کلاس پایگاه داده PokemonDB که از نوع abstract هست را ایجاد می کنیم.

    @Database(entities = {Pokemon.class},version = 2,exportSchema = false)
    public abstract class PokemonDB extends RoomDatabase {
            public abstract PokeDao pokeDao();
    }
    

    ما یک متد abstract از نوع بازگشت PokeDao اضافه کرده ایم.

    اکنون DatabaseModule را ایجاد خواهیم کرد که در صورت داشتن این پایگاه داده به نمونه سازی کمک می کند.

    @Module
    @InstallIn(ApplicationComponent.class)
    public class DataBaseModule {
    
        @Provides
        @Singleton
        public static PokemonDB providePokemonDB(Application application){
             return Room.databaseBuilder(application,PokemonDB.class,"Favorite Database")
                     .fallbackToDestructiveMigration()
                     .allowMainThreadQueries()
                     .build();
        }
    
        @Provides
        @Singleton
        public static PokeDao providePokeDao(PokemonDB pokemonDB){
            return pokemonDB.pokeDao();
        }
    }
    
    

    ما انوتیشن های @Module و@InstallIn  را در بالای این کلاس اضافه کرده ایم تا به Hilt بگوییم این یک ماژول است و برای application scope لازم است.

    @Singleton برای داشتن یک  نمونه واحد(instance single) از این بانک اطلاعاتی در کل برنامه استفاده می شود. اکنون کلاس Repository و کلاس PokemonViewModel را اصلاح خواهیم کرد.

    public class Repository {
    
        private PokeDao pokeDao;
        private PokeApiService apiService;
    
        @Inject
        public Repository(PokeDao pokeDao, PokeApiService apiService) {
            this.pokeDao = pokeDao;
            this.apiService = apiService;
        }
    
    
        public Observable<PokemonResponse> getPokemons(){
            return apiService.getPokemons();
        }
    
        public void insertPokemon(Pokemon pokemon){
            pokeDao.insertPokemon(pokemon);
        }
    
        public void deletePokemon(String pokemonName){
            pokeDao.deletePokemon(pokemonName);
        }
    
        public void deleteAll(){
            pokeDao.deleteAll();
        }
    
        public LiveData<List<Pokemon>> getFavoritePokemon(){
            return pokeDao.getFavoritePokemons();
        }
    }
    

     

    public class PokemonViewModel extends ViewModel {
        private static final String TAG = "PokemonViewModel";
    
        private Repository repository;
        private MutableLiveData<ArrayList<Pokemon>> pokemonList = new MutableLiveData<>();
        private LiveData<List<Pokemon>> favoritePokemonList = null;
    
        @ViewModelInject
        public PokemonViewModel(Repository repository) {
            this.repository = repository;
            favoritePokemonList = repository.getFavoritePokemon();
        }
    
        public MutableLiveData<ArrayList<Pokemon>> getPokemonList() {
            return pokemonList;
        }
    
        public void getPokemons(){
            repository.getPokemons()
                    .subscribeOn(Schedulers.io())
                    .map(new Function<PokemonResponse, ArrayList<Pokemon>>() {
                        @Override
                        public ArrayList<Pokemon> apply(PokemonResponse pokemonResponse) throws Throwable {
                            ArrayList<Pokemon> list = pokemonResponse.getResults();
                            for(Pokemon pokemon : list){
                                String url = pokemon.getUrl();
                                String[] pokemonIndex = url.split("/");
                                pokemon.setUrl("https://pokeres.bastionbot.org/images/pokemon/"+pokemonIndex[pokemonIndex.length-1] +".png");
                            }
                            Log.e(TAG, "apply: "+list.get(2).getUrl());
                            return list;
                        }
                    })
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(result -> pokemonList.setValue(result),
                            error-> Log.e(TAG, "getPokemons: " + error.getMessage() ));
        }
    
        public void insertPokemon(Pokemon pokemon){
            repository.insertPokemon(pokemon);
        }
        public void deletePokemon(String pokemonName){
            repository.deletePokemon(pokemonName);
        }
    
        public LiveData<List<Pokemon>> getFavoritePokemonList() {
            return favoritePokemonList;
        }
    
        public void getFavoritePokemon(){
           favoritePokemonList = repository.getFavoritePokemon();
        }
    
    
    
    }
    

    تنظیمات Room ، Hilt و ViewModel را به پایان رسانده ایم. اکنون Fragment و Activity را تنظیم خواهیم کرد.

     

    تنطیمات Fragment و Activity

    فرگمنتHome

    @AndroidEntryPoint
    public class Home extends Fragment {
        private static final String TAG = "Home";
        private HomeBinding binding;
        private PokemonViewModel viewModel;
        private PokemonAdapter adapter;
        private ArrayList<Pokemon> pokemonList;
    
        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            binding = HomeBinding.inflate(inflater,container,false);
            return binding.getRoot();
        }
    
        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
    
            viewModel = new ViewModelProvider(this).get(PokemonViewModel.class);
    
            initRecyclerView();
            observeData();
            setUpItemTouchHelper();
            viewModel.getPokemons();
        }
    
        private void setUpItemTouchHelper() {
            ItemTouchHelper.SimpleCallback simpleCallback = new ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.RIGHT) {
                @Override
                public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
                    return false;
                }
    
                @Override
                public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
                    int swipedPokemonPosition = viewHolder.getAdapterPosition();
                    Pokemon pokemon = adapter.getPokemonAt(swipedPokemonPosition);
                    viewModel.insertPokemon(pokemon);
                    adapter.notifyDataSetChanged();
                    Toast.makeText(getContext(),"Pokemon added to favorites.",Toast.LENGTH_SHORT).show();
                }
            };
    
            ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleCallback);
            itemTouchHelper.attachToRecyclerView(binding.pokemonRecyclerView);
        }
    
    
        private void observeData() {
            viewModel.getPokemonList().observe(getViewLifecycleOwner(), new Observer<ArrayList<Pokemon>>() {
                @Override
                public void onChanged(ArrayList<Pokemon> pokemons) {
                    Log.e(TAG, "onChanged: " + pokemons.size() );
                    adapter.updateList(pokemons);
                }
            });
        }
    
        private void initRecyclerView() {
            binding.pokemonRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
            adapter = new PokemonAdapter(getContext(),pokemonList);
            binding.pokemonRecyclerView.setAdapter(adapter);
        }
    }
    

    ما این fragment را با انوتیشن AndroidEntryPoint نوشته ایم ، بدین معنی که Hilt باید تمام وابستگی های این fragment را که می خواهد ارائه دهد.

    نکته مهمی که باید به آن توجه داشت:

    اگر کلاس Android را با انوتیشن AndroidEntryPoint مینویسید، باید کلاسهای Android که به آن بستگی دارد را با همین انوتیشن بنویسید. به عنوان مثال ، اگر یک fragment را با این انوتیشن مینویسید ، پس از آن باید Activity هایی را که در آن قسمت استفاده می کنید با این انوتیشن بنویسید.

    ما از کلاس ItemTouchHelper برای عملکرد swiping function استفاده کرده ایم و می خواهیم وقتی یک آیتم از لیست را تاچ کردیم و به سمت راست کشیدیم  pokemon را به موارد دلخواه اضافه کند و با کشیدن انگشت به سمت چپ pokemon مورد علاقه را حذف کرده و آنها را از لیست موارد دلخواه حذف کند.

    فرگمنت Favorites

    @AndroidEntryPoint
    public class Favorites extends Fragment {
        private FavoritesBinding binding;
        private PokemonViewModel viewModel;
        private PokemonAdapter adapter;
        private ArrayList<Pokemon> pokemonList;
    
        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            binding = FavoritesBinding.inflate(inflater,container,false);
            return binding.getRoot();
        }
    
        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
    
            viewModel = new ViewModelProvider(this).get(PokemonViewModel.class);
    
            initRecyclerView();
            setUpItemTouchHelper();
            observeData();
            //viewModel.getFavoritePokemon();
        }
    
        private void observeData() {
            viewModel.getFavoritePokemonList().observe(getViewLifecycleOwner(), new Observer<List<Pokemon>>() {
                @Override
                public void onChanged(List<Pokemon> pokemons) {
    
                    if(pokemons == null || pokemons.size() == 0)
                        binding.noFavoritesText.setVisibility(View.VISIBLE);
                    else{
                        ArrayList<Pokemon> list = new ArrayList<>();
                        list.addAll(pokemons);
                        adapter.updateList(list);
                    }
                }
            });
        }
    
        private void setUpItemTouchHelper() {
            ItemTouchHelper.SimpleCallback simpleCallback = new ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.LEFT) {
                @Override
                public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
                    return false;
                }
    
                @Override
                public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
                    int swipedPokemonPosition = viewHolder.getAdapterPosition();
                    Pokemon pokemon = adapter.getPokemonAt(swipedPokemonPosition);
                    viewModel.deletePokemon(pokemon.getName());
                    Toast.makeText(getContext(),"Pokemon removed from favorites.",Toast.LENGTH_SHORT).show();
                }
            };
    
            ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleCallback);
            itemTouchHelper.attachToRecyclerView(binding.favoritesRecyclerView);
        }
    
    
        private void initRecyclerView() {
            binding.favoritesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
            adapter = new PokemonAdapter(getContext(),pokemonList);
            binding.favoritesRecyclerView.setAdapter(adapter);
        }
    
    }
    

    ما همان کارهایی را انجام داده ایم که در فرگمنت Home انجام داده ایم و فقط تغییر جهت را در آیتم TouchHelper تغییر می دهیم.

    Main Activity

    @AndroidEntryPoint
    public class MainActivity extends AppCompatActivity {
        private ActivityMainBinding binding;
        private boolean isFavoriteListVisible = false;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            binding = ActivityMainBinding.inflate(getLayoutInflater());
            setContentView(binding.getRoot());
    
            getSupportFragmentManager().beginTransaction().replace(R.id.frameLayout,new Home())
                    .commit();
    
            binding.changeFragment.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if(isFavoriteListVisible){
                      isFavoriteListVisible = false;
                      binding.changeFragment.setText("Favorites");
                      getSupportFragmentManager().beginTransaction().replace(R.id.frameLayout,new Home())
                              .commit();
                    }
                    else {
                        isFavoriteListVisible = true;
                        binding.changeFragment.setText("Home");
                        getSupportFragmentManager().beginTransaction().replace(R.id.frameLayout,new Favorites())
                                .commit();
                    }
                }
            });
        }
    }
    
    

    در Activity ، از دکمه برای تغییر fragment  ها استفاده کرده ایم و از viewbinding هم استفاده کرده ایم.


    • نویسنده: میثم بابائی

    ارسال دیدگاه

    برای افزودن دیدگاه خود، نیاز است ابتدا وارد حساب کاربری‌تان شوید


    دیدگاه کاربران