
معماری 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هم استفاده کرده ایم.
جدیدترین ویدئوهای آموزشی
در بخش TV باگتو، آموزش های کوتاه و جدید را مشاهده نمایید