밀메이트 어플 개발 일지
Todo List
1. 클린 코드2. xml과 뷰모델 데이터바인딩
- food는 entity랑 xml이랑 데이터 바인딩 해도 될듯.
3. manifest 수정
- 다이어그램 그리기
- 테이블 추가
public class FoodApiHelper {
private static final String BASE_URL = "http://apis.data.go.kr/1471000/FoodNtrIrdntInfoService1/";
private static final String SERVICE_KEY = "GcathdXBPe7Iq8hBQV+9AIQvT3ZolP7IReNXxzkVfHgHqsnf29JywOSU01mENSUdeeaKp6igmU7EU1Spj/cIuw==";
private ApiService apiService;
private static FoodApiHelper instance = null;
public static FoodApiHelper getInstance(Context context) {
if(instance == null) {
synchronized(FoodApiHelper.class) {
if(instance == null) {
instance = new FoodApiHelper(context.getApplicationContext());
}
}
}
return instance;
}
public FoodApiHelper(Context context) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
apiService = retrofit.create(ApiService.class);
}
public Call<ResponseClass> getFoodNtrItdntList(String foodName) {
return apiService.getFoodNtrItdntList(SERVICE_KEY, foodName, null, null, null, null, "json");
}
public ApiService getApiService() {
return this.apiService;
}
public interface ApiService {
// GET 요청을 할 URL 구성. GET 요청을 할 메소드를 정의. 메소드의 리턴 타입은 Call<응답 클래스>로 지정.
@GET("getFoodNtrItdntList1") // 어노테이션 추가. getFoodNtrItdntList1은 API 메소드 이름
// 메소드의 매개변수는 쿼리 매개변수로 변환. 쿼리 매개변수는 @Query 어노테이션을 사용하여 정의. 쿼리 매개변수란 URL에 포함되는 매개변수를 의미.
// Call<응답 클래스>의 응답 클래스는 API 응답을 가진 클래스를 지정. 이 클래스는 JSON 응답을 자동으로 POJO로 변환할 때 사용.
// Call이란 Retrofit에서 제공하는 인터페이스. 비동기나 동기로 요청을 실행하고 응답을 받을 수 있음.
Call<ResponseClass> getFoodNtrItdntList(
@Query("serviceKey") String serviceKey, // string 형식의 쿼리 매개변수 추가. 왜? serviceKey는 string 형식이기 때문. api에서 확인 가능
@Query("desc_kor") String desc_kor,
@Query("pageNo") String pageNo,
@Query("numOfRows") String numOfRows,
@Query("bgn_year") String bgn_year,
@Query("animal_plant") String animal_plant,
@Query("type") String type
);
}
public class ResponseClass {
// 각 필드에 대응하는 프로퍼티를 정의
// JSON 필드 이름과 프로퍼티 이름이 같다면, @SerializedName 어노테이션은 생략가능.
@SerializedName("header")
private Header header;
@SerializedName("body")
private Body body;
public Body getBody() {
return body;
}
public class Header {
@SerializedName("resultCode")
private String resultCode;
@SerializedName("resultMsg")
private String resultMsg;
// getters and setters...
}
public class Body {
@SerializedName("totalCount")
private int totalCount;
@SerializedName("items")
private List<Item> items;
// getters and setters...
public List<Item> getItems() {
return items;
}
public class Item {
@SerializedName("DESC_KOR")
private String food_name;
@SerializedName("SERVING_WT")
private float food_1serving;
@SerializedName("NUTR_CONT1")
private float food_kcal;
@SerializedName("NUTR_CONT2")
private float food_carbohydrates;
@SerializedName("NUTR_CONT3")
private float food_protein;
@SerializedName("NUTR_CONT4")
private float food_fat;
@SerializedName("ANIMAL_PLANT")
private String food_company;
// getters and setters...
public String getFood_name() {
return food_name;
}
public float getFood_1serving() {
return food_1serving;
}
public float getFood_kcal() {
return food_kcal;
}
public float getFood_carbohydrates() {
return food_carbohydrates;
}
public float getFood_protein() {
return food_protein;
}
public float getFood_fat() {
return food_fat;
}
public String getFood_company() {
return food_company;
}
}
}
}
}
@Dao
public interface FoodDao {
@Insert
void insertFood(Food food);
@Update
void updateFood(Food food);
@Delete
void deleteFood(Food food);
@Query("SELECT * FROM food")
LiveData<List<Food>> getAllFoods();
// 특정 음식의 좋아요 여부를 가져옵니다.
@Query("SELECT * FROM food WHERE foodLike = 1")
LiveData<List<Food>> getLikedFoods();
@Query("SELECT * FROM food WHERE foodName = :foodName")
LiveData<List<Food>> getFoodByName(String foodName);
// 비동기적으로 데이터를 가져옵니다.
@Query("SELECT * FROM food WHERE foodName = :foodName LIMIT 1")
Food getFoodByNameSync(String foodName);
}
@Dao
public interface MealDao {
@Insert
void insertMeal(Meal meal);
@Update
void updateMeal(Meal meal);
@Delete
void deleteMeal(Meal meal);
// 모든 Meal을 가져옵니다.
@Query("SELECT * FROM meal")
LiveData<List<Meal>> getAllMeals();
// 특정 날짜의 Meal을 가져옵니다.
@Query("SELECT * FROM meal WHERE mealDate = :mealDate")
LiveData<List<Meal>> getMealsByDate(String mealDate);
// 특정 음식의 Meal을 가져옵니다.
@Query("SELECT * FROM meal WHERE foodIndex = :foodIndex")
LiveData<List<Meal>> getMealsByFoodIndex(int foodIndex);
// 체크가 된 Meal을 가져옵니다.
@Query("SELECT * FROM meal WHERE checked = 1")
LiveData<List<Meal>> getCheckedMeals();
}
@Dao
public interface UserDao {
@Insert
void insertUser(User user);
@Update
void updateUser(User user);
@Delete
void deleteUser(User user);
@Query("SELECT * FROM user WHERE user_name = :userName")
LiveData<User> getUserByName(String userName);
}
// version = 1 : 처음 생성할 때 버전 1로 생성. 이후에 업데이트할 때마다 버전을 올려줘야 함.
@Database(entities = {Meal.class, Food.class, User.class}, version = 2)
public abstract class AppDatabase extends RoomDatabase {
private static final String DATABASE_NAME = "app_database";
private static AppDatabase instance;
public abstract MealDao mealDao(); // MealDao를 사용하기 위한 메소드.
public abstract FoodDao foodDao();
public abstract UserDao userDao();
public static synchronized AppDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, DATABASE_NAME)
.addMigrations(MIGRATION_1_2)
.build();
}
return instance;
}
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// food 테이블에 foodLike 컬럼을 추가.
database.execSQL("ALTER TABLE food ADD COLUMN foodLike INTEGER DEFAULT 0");
}
};
}
@Entity(tableName = "food")
public class Food {
@PrimaryKey(autoGenerate = true)
private int foodIndex;
private String foodName;
private float food1serving;
private float foodKcal;
private float foodCarbohydrates;
private float foodProtein;
private float foodFat;
private String food_company;
private int foodLike = 0; // default value: 0
public Food(String foodName, float food1serving, float foodKcal, float foodCarbohydrates, float foodProtein, float foodFat, String food_company, int foodLike) {
this.foodName = foodName;
this.food1serving = food1serving;
this.foodKcal = foodKcal;
this.foodCarbohydrates = foodCarbohydrates;
this.foodProtein = foodProtein;
this.foodFat = foodFat;
this.food_company = food_company;
this.foodLike = foodLike;
}
//생성자 오버로딩
@Ignore
public Food(String foodName, float food1serving, float foodKcal, float foodCarbohydrates, float foodProtein, float foodFat, String food_company) {
this.foodName = foodName;
this.food1serving = food1serving;
this.foodKcal = foodKcal;
this.foodCarbohydrates = foodCarbohydrates;
this.foodProtein = foodProtein;
this.foodFat = foodFat;
this.food_company = food_company;
}
// getter methods...
public int getFoodIndex() { return foodIndex; }
public String getFoodName() { return foodName; }
public float getFood1serving() { return food1serving; }
public float getFoodKcal() { return foodKcal; }
public float getFoodCarbohydrates() { return foodCarbohydrates; }
public float getFoodProtein() { return foodProtein; }
public float getFoodFat() { return foodFat; }
public String getFood_company() { return food_company; }
public int getFoodLike() { return foodLike; }
//test
public void setFoodIndex(int foodIndex) {this.foodIndex = foodIndex;}
@Override
public String toString() {
return "FoodIndex: " + foodIndex + "\n" + "FoodName: " + foodName + "\n" + "Food1serving: " + food1serving + "\n" +
"FoodKcal: " + foodKcal + "\n" + "FoodCarbohydrates: " + foodCarbohydrates + "\n" + "FoodProtein: " + foodProtein + "\n" +
"FoodFat: " + foodFat + "\n" + "FoodCompany: " + food_company + "\n" + "FoodLike: " + foodLike;
}
}
@Entity(tableName = "meal", foreignKeys = @ForeignKey(entity = Food.class, parentColumns = "foodIndex", childColumns = "foodIndex"))
public class Meal {
@PrimaryKey(autoGenerate = true)
private int mealIndex;
private String mealDate;
private int mealCnt;
private int mealFoodAmount;
@ColumnInfo(name = "foodIndex")
private int foodIndex;
private int checked;
public Meal(String mealDate, int mealCnt, int mealFoodAmount, int foodIndex, int checked) {
this.mealDate = mealDate;
this.mealCnt = mealCnt;
this.mealFoodAmount = mealFoodAmount;
this.foodIndex = foodIndex;
this.checked = checked;
}
// getter and setter methods...
public int getMealIndex() {return mealIndex;}
public String getMealDate() {return mealDate;}
public int getMealCnt() {return mealCnt;}
public int getMealFoodAmount() {return mealFoodAmount;}
public void setMealFoodAmount(int mealFoodAmount) {this.mealFoodAmount = mealFoodAmount;}
public int getFoodIndex() {return foodIndex;}
public int getChecked() {return checked;}
public void setChecked(int checked) {this.checked = checked;}
//test
public void setMealIndex(int mealIndex) {this.mealIndex = mealIndex;}
}
@Entity(tableName = "user")
public class User {
@PrimaryKey
@NonNull
public String user_name;
//TODO : 지환이한테 물어보기
public float user_kcal;
public float user_height;
public float user_weight;
public int user_purpose;
public int user_pwd;
public int user_work;
//TODO : 생성자 만들기.
// TODO : getter and setter methods...
}
public class FoodApiRepository {
private FoodApiHelper foodApiHelper;
private static FoodApiRepository instance = null;
private FoodApiHelper.ApiService apiService;
private MutableLiveData<List<Food>> searchResults;
private static final String SERVICE_KEY = "GcathdXBPe7Iq8hBQV+9AIQvT3ZolP7IReNXxzkVfHgHqsnf29JywOSU01mENSUdeeaKp6igmU7EU1Spj/cIuw==";
public FoodApiRepository(Context context) {
foodApiHelper = FoodApiHelper.getInstance(context);
apiService = foodApiHelper.getApiService();
searchResults = new MutableLiveData<>();
}
public static FoodApiRepository getInstance(Context context) {
if (instance == null) {
synchronized(FoodApiRepository.class) {
if(instance == null) {
instance = new FoodApiRepository(context.getApplicationContext());
}
}
}
return instance;
}
public LiveData<List<Food>> searchFood(String foodName) {
MutableLiveData<List<Food>> searchResults = new MutableLiveData<>();
Call<FoodApiHelper.ResponseClass> call = apiService.getFoodNtrItdntList( SERVICE_KEY, foodName, null, null, null, null, "json" );
call.enqueue(new Callback<FoodApiHelper.ResponseClass>() { // Callback 인터페이스를 구현한 익명 클래스 정의. 응답을 받았을 때 호출되는 메소드를 정의.
@Override
// onResponse 메소드: 응답을 성공적으로 받았을 때 호출되는 메소드
public void onResponse(Call<FoodApiHelper.ResponseClass> call, Response<FoodApiHelper.ResponseClass> response) {
if (response.isSuccessful() && response.body() != null && response.body().getBody() != null) {
List<FoodApiHelper.ResponseClass.Body.Item> items = response.body().getBody().getItems();
if (items != null) {
List<Food> foods = new ArrayList<>();
for (FoodApiHelper.ResponseClass.Body.Item item : items) {
foods.add(new Food(item.getFood_name(), item.getFood_1serving(), item.getFood_kcal(), item.getFood_carbohydrates(), item.getFood_protein(), item.getFood_fat(), item.getFood_company()));
}
searchResults.setValue(foods);
}
}
}
@Override
public void onFailure(Call<FoodApiHelper.ResponseClass> call, Throwable t) {
// Handle failure
}
});
return searchResults;
}
public Call<FoodApiHelper.ResponseClass> getFoodNtrItdntList(String foodName) {
return apiService.getFoodNtrItdntList( SERVICE_KEY, foodName, null, null, null, null, "json" );
}
}
public class FoodRepository {
private static FoodRepository instance = null;
private final FoodDao foodDao;
private final AppDatabase db;
public static FoodRepository getInstance(Context context) {
if (instance == null) {
synchronized(FoodRepository.class) {
if(instance == null) {
instance = new FoodRepository(context.getApplicationContext());
}
}
}
return instance;
}
public FoodRepository(Context context) {
db = Room.databaseBuilder(context, AppDatabase.class, "app-database").build();
foodDao = db.foodDao();
}
// 이 클래스는 새로운 스레드에서 데이터베이스 작업을 수행.
// 이는 Room 라이브러리의 중요한 특징. 메인 스레드에서 데이터베이스 작업을 하면 UI가 멈추는 현상이 발생할 수 있기 때문.
// 이로 인해 사용자 경험이 저하 가능성. 따라서 Room 라이브러리는 메인 스레드에서 데이터베이스 작업을 하지 못하도록 막음.
public void insertFood(Food food) {
Executors.newSingleThreadExecutor().execute(() -> {
foodDao.insertFood(food);
});
}
public void updateFood(Food food) {
Executors.newSingleThreadExecutor().execute(() -> {
foodDao.updateFood(food);
});
}
public void deleteFood(Food food) {
Executors.newSingleThreadExecutor().execute(() -> {
foodDao.deleteFood(food);
});
}
// LiveData를 이용하여 데이터를 가져옵니다.
public LiveData<List<Food>> getAllFoods() {
return foodDao.getAllFoods();
}
// 특정 음식의 좋아요 여부를 가져옵니다.
public LiveData<List<Food>> getLikedFoods() {
return foodDao.getLikedFoods();
}
public LiveData<List<Food>> getFoodByName(String foodName) {
return foodDao.getFoodByName(foodName);
}
public Food getFoodByNameSync(String foodName) {
return foodDao.getFoodByNameSync(foodName);
}
}
public class MealRepository {
private static MealRepository instance = null;
private final MealDao mealDao;
private final AppDatabase db;
/* 싱글톤 패턴을 적용하여, getInstance()를 통해 객체를 가져옴.
why? 여러 뷰모델에서 하나의 레포지토리를 공유함으로 데이터 충돌 방지
- 레포지토리는 앱이 실행되는 동안 계속 존재. (뷰모델은 액티비티가 살아있는 동안만 존재)
Application의 context를 전달함으로써 계속해서 Application의 변화에 따라 부모격인 context에 반영*/
public static MealRepository getInstance(Context context) {
if (instance == null) {
synchronized(MealRepository.class) {
if(instance == null) {
instance = new MealRepository(context.getApplicationContext());
}
}
}
return instance;
}
public MealRepository(Context context) {
db = Room.databaseBuilder(context, AppDatabase.class, "app-database").build();
mealDao = db.mealDao();
}
// 이 클래스는 새로운 스레드에서 데이터베이스 작업을 수행.
public void insertMeal(Meal meal) {
Executors.newSingleThreadExecutor().execute(() -> {
mealDao.insertMeal(meal);
});
}
public void updateMeal(Meal meal) {
Executors.newSingleThreadExecutor().execute(() -> {
mealDao.updateMeal(meal);
});
}
public void deleteMeal(Meal meal) {
Executors.newSingleThreadExecutor().execute(() -> {
mealDao.deleteMeal(meal);
});
}
// LiveData를 이용하여 데이터를 가져옵니다.
public LiveData<List<Meal>> getAllMeals() {
return mealDao.getAllMeals();
}
public LiveData<List<Meal>> getMealsByDate(String mealDate) {
return mealDao.getMealsByDate(mealDate);
}
public LiveData<List<Meal>> getMealsByFoodIndex(int foodIndex) {
return mealDao.getMealsByFoodIndex(foodIndex);
}
public LiveData<List<Meal>> getCheckedMeals() {
return mealDao.getCheckedMeals();
}
}
public class UserRepository {
private final UserDao userDao;
private final AppDatabase db;
public UserRepository(Context context) {
db = Room.databaseBuilder(context, AppDatabase.class, "app-database").build();
userDao = db.userDao();
}
public void insertUser(User user) {
Executors.newSingleThreadExecutor().execute(() -> {
userDao.insertUser(user);
});
}
public void updateUser(User user) {
Executors.newSingleThreadExecutor().execute(() -> {
userDao.updateUser(user);
});
}
public void deleteUser(User user) {
Executors.newSingleThreadExecutor().execute(() -> {
userDao.deleteUser(user);
});
}
public LiveData<User> getUserByName(String userName) {
return userDao.getUserByName(userName);
}
}
public class FoodApiViewModel extends ViewModel {
private FoodApiRepository foodApiRepository;
public FoodApiViewModel(Application application) {
super();
foodApiRepository = FoodApiRepository.getInstance(application);
}
public Call<FoodApiHelper.ResponseClass> getFoodNtrItdntList(String foodName) {
return foodApiRepository.getFoodNtrItdntList(foodName);
}
public LiveData<List<Food>> searchFood(String foodName) {
return foodApiRepository.searchFood(foodName);
}
}
public class FoodApiViewModelFactory implements ViewModelProvider.Factory {
private Application mApplication;
public FoodApiViewModelFactory(Application application) {
mApplication = application;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
return (T) new FoodApiViewModel(mApplication);
}
}
public class FoodViewModel extends AndroidViewModel {
// private final로 선언을 왜 해? -> 생성자에서 초기화를 해줘야 하기 때문에
private final FoodRepository repository;
private final LiveData<List<Food>> allFoods;
private final MutableLiveData<Food> foodLiveData = new MutableLiveData<>();
private MutableLiveData<String> apiResult = new MutableLiveData<>();
public FoodViewModel (Application application) {
super(application);
repository = FoodRepository.getInstance(application);
//repository = new FoodRepository(application);
allFoods = repository.getAllFoods();
}
public LiveData<List<Food>> getAllFoods() { return allFoods; }
public void insert(Food food) {
new Thread(() -> {
Food existingFood = repository.getFoodByNameSync(food.getFoodName());
if (existingFood == null) {
repository.insertFood(food);
}
}).start();
}
public void update(Food food) { repository.updateFood(food); }
public void delete(Food food) { repository.deleteFood(food); }
// 특정 음식의 좋아요 여부를 가져옵니다.
public LiveData<List<Food>> getLikedFoods() { return repository.getLikedFoods(); }
public void setClickedFood(Food food) {foodLiveData.setValue(food);}
public LiveData<Food> getFoodLiveData() {return foodLiveData;}
public LiveData<String> getApiResult() {
return apiResult;
}
public void searchFood(String foodName) {
FoodApiRepository foodApiRepository = FoodApiRepository.getInstance(getApplication());
LiveData<List<Food>> result = foodApiRepository.searchFood(foodName);
// 결과를 문자열로 변환하고 apiResult를 업데이트합니다.
apiResult.setValue(result.toString());
}
}
public class MealViewModel extends AndroidViewModel {
private final MealRepository repository;
private final LiveData<List<Meal>> allMeals;
public MealViewModel (Application application) {
super(application);
repository = MealRepository.getInstance(application);
/*repository = new MealRepository(application);*/
allMeals = repository.getAllMeals();
}
public LiveData<List<Meal>> getAllMeals() { return allMeals; }
public void insert(Meal meal) { repository.insertMeal(meal); }
public void update(Meal meal) { repository.updateMeal(meal); }
public void delete(Meal meal) { repository.deleteMeal(meal); }
}
public class UserViewModel extends AndroidViewModel {
private final UserRepository repository;
public UserViewModel (Application application) {
super(application);
repository = new UserRepository(application);
}
public void insert(User user) { repository.insertUser(user); }
public void update(User user) { repository.updateUser(user); }
public void delete(User user) { repository.deleteUser(user); }
}
public class MainActivity extends AppCompatActivity {
private FoodViewModel foodViewModel;
private FoodApiViewModel foodApiViewModel;
private EditText editTextFoodName;
private Button buttonSaveFood;
private Button buttonSearchFood;
private FoodListAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editTextFoodName = findViewById(R.id.editTextFoodName);
buttonSaveFood = findViewById(R.id.buttonSaveFood);
buttonSearchFood = findViewById(R.id.buttonSearchFood);
foodViewModel = new ViewModelProvider(this).get(FoodViewModel.class);
// factory를 통해 ViewModel을 생성한다.
FoodApiViewModelFactory factory = new FoodApiViewModelFactory(getApplication());
foodApiViewModel = new ViewModelProvider(this, factory).get(FoodApiViewModel.class);
RecyclerView recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new FoodListAdapter(new FoodListAdapter.FoodDiff(), foodViewModel, true);
recyclerView.setAdapter(adapter);
// 아이템 클릭 리스너 설정
adapter.setOnItemClickListener(food -> {
foodViewModel.insert(food);
LiveData<List<Food>> liveData = foodViewModel.getAllFoods();
liveData.observe(this, foods -> {
adapter.submitList(foods);
});
});
buttonSearchFood.setOnClickListener(v -> {
String foodName = editTextFoodName.getText().toString();
LiveData<List<Food>> liveData = foodApiViewModel.searchFood(foodName);
liveData.observe(this, foods -> {
adapter.submitList(foods);
});
});
buttonSaveFood.setOnClickListener(view -> {
LiveData<List<Food>> liveData = foodViewModel.getAllFoods();
liveData.observe(this, foods -> {
adapter.submitList(foods);
});
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
});
}
}
public class SecondActivity extends AppCompatActivity {
private FoodViewModel foodViewModel;
private RecyclerView recyclerViewSavedFoods;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
recyclerViewSavedFoods = findViewById(R.id.recyclerViewSavedFoods);
foodViewModel = new ViewModelProvider(this).get(FoodViewModel.class);
RecyclerView recyclerView = findViewById(R.id.recyclerViewSavedFoods);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
final FoodListAdapter adapter = new FoodListAdapter(new FoodListAdapter.FoodDiff(), foodViewModel, false);
recyclerView.setAdapter(adapter);
foodViewModel.getAllFoods().observe(this, foods -> {
adapter.submitList(foods);
});
adapter.setOnItemClickListener(food -> {
new AlertDialog.Builder(this)
.setTitle("Food Details")
.setMessage(food.toString())
.setPositiveButton(android.R.string.ok, null)
.show();
});
}
}
public class FoodListAdapter extends ListAdapter<Food, FoodListAdapter.FoodViewHolder> {
private final FoodViewModel viewModel;
private boolean isMain; // 메인 액티비티인지 여부
protected FoodListAdapter(@NonNull DiffUtil.ItemCallback<Food> diffCallback, FoodViewModel viewModel, boolean isMain) {
super(diffCallback);
this.viewModel = viewModel;
this.isMain = isMain;
}
@NonNull
@Override
public FoodViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
RecyclerviewItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.recyclerview_item, parent, false);
return new FoodViewHolder(binding, listener);
}
@Override
public void onBindViewHolder(@NonNull FoodViewHolder holder, int position) {
Food current = getItem(position);
holder.bind(current, listener);
holder.itemView.setOnClickListener(v -> {
if (isMain) {
// 메인 액티비티에서는 DB에 저장
viewModel.insert(current);
} else {
// 세컨드 액티비티에서는 아이템 클릭 리스너 호출
if (listener != null) {
listener.onItemClick(current);
}
}
});
}
static class FoodDiff extends DiffUtil.ItemCallback<Food> {
@Override
public boolean areItemsTheSame(@NonNull Food oldItem, @NonNull Food newItem) {
return oldItem == newItem;
}
@Override
public boolean areContentsTheSame(@NonNull Food oldItem, @NonNull Food newItem) {
return oldItem.getFoodName().equals(newItem.getFoodName());
}
}
static class FoodViewHolder extends RecyclerView.ViewHolder {
private final RecyclerviewItemBinding binding;
private final OnItemClickListener listener; // listener를 추가합니다.
private FoodViewHolder(RecyclerviewItemBinding binding, OnItemClickListener listener) {
super(binding.getRoot());
this.binding = binding;
this.listener = listener; // listener를 초기화합니다.
}
public void bind(Food food, OnItemClickListener listener) {
binding.setFood(food);
binding.executePendingBindings(); // 바인딩을 즉시 실행
// 아이템 클릭 리스너 설정
itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onItemClick(food);
}
});
}
}
public interface OnItemClickListener {
void onItemClick(Food food);
}
private OnItemClickListener listener;
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/editTextFoodName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="Enter Food Name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/buttonSearchFood"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/buttonSearchFood"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="검색"
app:layout_constraintStart_toEndOf="@id/editTextFoodName"
app:layout_constraintEnd_toStartOf="@id/buttonSaveFood"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/buttonSaveFood"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="저장된 데이터"
app:layout_constraintStart_toEndOf="@id/buttonSearchFood"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/buttonSaveFood"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textViewTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Saved Foods"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewSavedFoods"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/textViewTitle"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="food"
type="android.anonymous.db_test.model.entity.Food" />
</data>
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:text="@{food.foodName}"/>
</layout>