API + DB(SQLite) 연동

jiyoon·2023년 5월 28일
0

밀메이트

목록 보기
3/3
post-thumbnail

밀메이트 어플 개발 일지
Todo List
1. 클린 코드

2. xml과 뷰모델 데이터바인딩
- food는 entity랑 xml이랑 데이터 바인딩 해도 될듯. 
3. manifest 수정
  1. 다이어그램 그리기
  2. 테이블 추가

model

api

FoodApiHelper.java

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

FoodDao.java

@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);
}

MealDao.java

@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();
}

UserDao

@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);
}

database

Appdatabase.java

// 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

Food.java

@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;
    }
}

Meal.java

@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;}
}

User.java

@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...
}

repository

FoodApiRepository.java

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" );
    }
}

FoodRepository.java

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);
    }
}

MealRepojitory.java

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();
    }
}

UserRepository.java

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);
    }
}

viewModel

FoodApiViewModel.java

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);
    }
}

FoodApiViewModelFactory.java

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);
    }
}

FoodViewModel.java

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());
    }
}

MealViewModel.java

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); }
}

UserViewModel.java

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); }
}

view

MainActivity.java

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);
        });
    }
}

SecondActivity.java

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();
        });
    }
}

FoodListAdapter.java

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;
    }
}

layout

activity_main.xml

<?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>

activity_second.xml

<?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>

listview.xml

<?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>

recyclerview_item.xml

<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>
profile
주니어 개발자

0개의 댓글