티스토리 뷰

프로젝트/개인

[Android] Shopping App

wnsgur0329 2019. 10. 23. 10:16

웹이든 앱이든 개발자들이 한 번씩은 경험해 보는 것이 쇼핑몰일 것이다. 그만큼 기본적으로 할 수 있어야 하는 부분들을 할 수 있고, 개발실력 향상에 도움이 되어서가 아닌가 생각된다.

안드로이드 개발 실력을 스스로 확인하기위해, 약 한 달의 시간에 걸쳐 천천히 쇼핑몰 개발을 진행하였다.

아래는 개발하기 전에 세웠던 목표들이다.

  • 로딩화면에서 애니메이션을 넣어 로그인 화면 전환을 부드럽게 만들어보자.
  • RecyclerView를 사용하자.
  • BottomNavigationBar 를 사용하자.
  • SQLite를 써서 개발해보자.
  • 자동로그인 기능도 구현해보자.

결론적으로 위 목표를 모두 실현했다.

 

왼쪽부터 로그인 화면, 회원가입 화면, 홈 화면이다.

 

왼쪽부터 쇼핑 홈 화면, 장바구니 화면, 개인정보 화면이다.

 

 

https://youtu.be/Suzz_FGzeu0

 

 

코드

 

- Login Activity -

우선, 프로젝트를 막 시작하며 Animation을 구현하면서 써놓았던 글이 있는데, 거기에 로고와 약간의 수정만 한 후 커스텀 로그인 폼을 추가해서 로딩 화면을 완성했다. 

private PreferenceManager pManager;

...

private void startAnime() {
        logoAni = new TranslateAnimation(0, 0, 0, -250); // from 어디서 to 어디까지 이동할건지. 가운데를 중심으로 위, 왼쪽: - 아래, 오른쪽: +
        logoAni.setDuration(2000); // 지속시간
        logoAni.setFillAfter(true); // 이동 후 이동한 자리에 남아있을건지
        logoAni.setStartOffset(1500); // 딜레이
        logoAni.setInterpolator(new AccelerateDecelerateInterpolator()); // interpolator 설정. AccelerteDecelerate : 시작지점에 가속했다 종료시점에 감속

        loginFormAni = new AlphaAnimation(0, 1);
        loginFormAni.setDuration(1000);
        loginFormAni.setStartOffset(3000);

        imgView.setAnimation(logoAni); // 애니메이션 세팅
        loginForm.setAnimation(loginFormAni);
    }


public void loginCheck() {
        String loginId = pManager.getString(this, "user_id");

        if(loginId.length() != 0) { // preference가 비어있지 않으면 바로 Main실행.
            startMainActivity();
        }
    }
    
private boolean accountCheck() {
        //db에 접근해서 id, pw 확인 후 일치 시 true, 불일치시 false return.
        String idValue = String.valueOf(id.getText());
        String pwValue = String.valueOf(pw.getText());

        if(idValue.equals("") || pwValue.equals("")) {
            return false;
        }

        String dbId = dbHelper.getUserId(idValue);
        String dbPw = dbHelper.getUserPw(idValue);

        if(dbId.equals(idValue)) {
            if(dbPw.equals(pwValue)) {
                Toast.makeText(this, "환영합니다.", Toast.LENGTH_SHORT).show();
                return true;
            }
            Toast.makeText(this, "비밀번호를 확인해주세요.", Toast.LENGTH_SHORT).show();
            return false;
        }

        setEmptyEt();
        Toast.makeText(this, "존재하지 않는 아이디입니다.", Toast.LENGTH_SHORT).show();
        return false;
    }

    private void setEmptyEt() {
        id.setText("");
        pw.setText("");
    }

...

 

- Register Activity -

public void onOkBtnClick(View v) {
        if(isEmpty()) { // 비어있는 칸 없는지 확인. 있으면 true 반환
           return;
        } else if(isDuplicateId()) { // ID값 중복된 것 있는지 확인. 있으면 true 반환
            Toast.makeText(this, "이미 있는 ID 입니다. ID를 변경해주세요.", Toast.LENGTH_SHORT).show();
            return;
        } else if(!isCorreectPw()) { // PW, PwChk 값 같은지 확인. 같으면 true 반환
            Toast.makeText(this, "비밀번호와 비밀번호 확인의 값이 다릅니다. 다시 확인해주세요.", Toast.LENGTH_SHORT).show();
            return;
        }
        // Email 포맷 체크, %@% 아니면 DB에 안들어감.

        // DB에 값 집어넣기
        UserBean userBean = new UserBean();

        userBean.setName(String.valueOf(name.getText()));
        userBean.setEmail(String.valueOf(email.getText()));
        userBean.setId(String.valueOf(id.getText()));
        userBean.setPassword(String.valueOf(pw.getText()));
        userBean.setGender(String.valueOf(selectGender));
        userBean.setYears(String.valueOf(selectYears));

        dbHelper.insertUser(userBean);
        Toast.makeText(this, "회원가입이 완료되었습니다.", Toast.LENGTH_SHORT).show();
        finish();
    }

 

- Main Activity -

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

        // 첫 화면 지정
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.frameLayout, homeFragment).commitAllowingStateLoss();

        BottomNavigationView bottomNav = findViewById(R.id.bottom_navigation_view);
        bottomNav.setOnNavigationItemSelectedListener(new ItemSelectListener());
    }

    private class ItemSelectListener implements BottomNavigationView.OnNavigationItemSelectedListener{

        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
            FragmentTransaction transaction = fragmentManager.beginTransaction();

            switch (menuItem.getItemId()) {
                case R.id.nav_home:
                    transaction.replace(R.id.frameLayout, homeFragment).commitAllowingStateLoss();
                    break;

                case R.id.nav_shop:
                    transaction.replace(R.id.frameLayout, shopFragment).commitAllowingStateLoss();
                    break;

                case R.id.nav_cart:
                    transaction.replace(R.id.frameLayout, cartFragment).commitAllowingStateLoss();
                    break;

                case R.id.nav_my:
                    transaction.replace(R.id.frameLayout, myFragment).commitAllowingStateLoss();
                    break;
            }
            return true;
        }
    }

    @Override
    public void onBackPressed() {
        ActivityCompat.finishAffinity(this); // app 종료
    }

마지막 onBackPressed()의 ActivityCompat.finishAffinity(this) 는, RootActivity까지 나가 finish()를 하지 말고 바로 종료시키고 싶었기 때문에 넣었다.

 

- Home Fragment -

    private static final int INTERVAL_TIME = 3800;

    private View rootView;
    private ViewFlipper viewFlipper;
    private GridView gridView;
    private HomeGridAdapter adapter;
    private ArrayList<ProductBean> data;
    private ProductDBHelper dbHelper;

    int images[] = {
            R.drawable.slide_image_1,
            R.drawable.slide_image_2,
            R.drawable.slide_image_3,
            R.drawable.slide_image_4
    };

    // 메인. 슬라이드 형식 화면 절반치 광고, 아래에 상품 6개 정도 보여주기
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.activity_home_fragment, container, false);
        viewFlipper = view.findViewById(R.id.imageSlide);

        for(int image : images)
            flipperImages(image);

        showProduct();

        return view;
    }

    private void flipperImages(int image) {
        ImageView imageView = new ImageView(getContext());
        imageView.setBackgroundResource(image);

        viewFlipper.addView(imageView);
        viewFlipper.setFlipInterval(INTERVAL_TIME);
        viewFlipper.setAutoStart(true);

        viewFlipper.setInAnimation(getContext(), R.anim.slide_in_anim);
        viewFlipper.setOutAnimation(getContext(), R.anim.slide_out_anim);
    }

    private void showProduct() {
        dbHelper = ProductDBHelper.getInstance(getContext());
        data = dbHelper.getRandomProduct();

        gridView = view.findViewById(R.id.gridView);
        adapter = new HomeGridAdapter(getContext(), data);
        gridView.setAdapter(adapter);
    }

Fragment를 이렇게 많이쓴 프로젝트는 처음이었다. Activity와 달리 onCreateView를 사용해야 하며, findViewById를 할 때에는 따로 view를 inflate 한 후 사용해야 한다는 점이 어색했었다.

 

- UserDBHelper -

public class UserDBHelper extends SQLiteOpenHelper {

    private static UserDBHelper dbHelper = null;

    public static final String DATABASE_NAME = "userdb";
    public static final String TABLE_NAME = "user";
    public static final int DB_VERSION = 1;

    public static final String COL_0 = "serialNumber";
    public static final String COL_1 = "name";
    public static final String COL_2 = "email";
    public static final String COL_3 = "id";
    public static final String COL_4 = "password";
    public static final String COL_5 = "gender";
    public static final String COL_6 = "years";

    public static UserDBHelper getInstance(Context context){
        if(dbHelper == null){
            dbHelper = new UserDBHelper(context.getApplicationContext());
        }

        return dbHelper;
    }

    private UserDBHelper(Context context){
        super(context, DATABASE_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "create table " + TABLE_NAME + " ("
                + COL_0 + " integer primary key autoincrement, "
                + COL_1 + " text not null,"
                + COL_2 + " text not null check (email like '%@%'),"
                + COL_3 + " text not null unique,"
                + COL_4 + " text not null,"
                + COL_5 + " text not null,"
                + COL_6 + " text not null "
                + ")";

        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        String sql = "drop table " + TABLE_NAME;
        db.execSQL(sql);
        onCreate(db);
    }

    public long insertUser(UserBean user){
        SQLiteDatabase db = getWritableDatabase();
        ContentValues value = new ContentValues();

        value.put(COL_1, user.getName());
        value.put(COL_2, user.getEmail());
        value.put(COL_3, user.getId());
        value.put(COL_4, user.getPassword());
        value.put(COL_5, user.getGender());
        value.put(COL_6, user.getYears());

        return db.insert(TABLE_NAME, null, value);
    }

    public ArrayList<UserBean> getAllUser(){
        SQLiteDatabase db = getReadableDatabase();
        Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, null);
        ArrayList<UserBean> result = new ArrayList<>();

        while(cursor.moveToNext()){
            UserBean user = new UserBean();
            user.setSerialNumber(cursor.getInt(cursor.getColumnIndex(COL_0)));
            user.setName(cursor.getString(cursor.getColumnIndex(COL_1)));
            user.setEmail(cursor.getString(cursor.getColumnIndex(COL_2)));
            user.setId(cursor.getString(cursor.getColumnIndex(COL_3)));
            user.setPassword(cursor.getString(cursor.getColumnIndex(COL_4)));
            user.setGender(cursor.getString(cursor.getColumnIndex(COL_5)));
            user.setYears(cursor.getString(cursor.getColumnIndex(COL_6)));
            result.add(user);
        }

        return result;
    }

    public ArrayList<UserBean> getUserById(String id){
        SQLiteDatabase db = getReadableDatabase();
        Cursor cursor = db.query(TABLE_NAME, null, COL_3 + "=?", new String[] {id}, null, null, null);
        ArrayList<UserBean> result = new ArrayList<>();

        while(cursor.moveToNext()){
            UserBean user = new UserBean();

            user.setSerialNumber(cursor.getInt(cursor.getColumnIndex(COL_0)));
            user.setName(cursor.getString(cursor.getColumnIndex(COL_1)));
            user.setEmail(cursor.getString(cursor.getColumnIndex(COL_2)));
            user.setId(cursor.getString(cursor.getColumnIndex(COL_3)));
            user.setPassword(cursor.getString(cursor.getColumnIndex(COL_4)));
            user.setGender(cursor.getString(cursor.getColumnIndex(COL_5)));
            user.setYears(cursor.getString(cursor.getColumnIndex(COL_6)));

            result.add(user);
        }

        return result;
    }

    public String getUserId(String id){
        SQLiteDatabase db = getReadableDatabase();
        Cursor cursor = db.query(TABLE_NAME, null, COL_3 + "=?", new String[] {id}, null, null, null);
        String result = "";

        while(cursor.moveToNext()){
            result = cursor.getString(cursor.getColumnIndex(COL_3));
        }

        return result;
    }

    public String getUserPw(String id){
        SQLiteDatabase db = getReadableDatabase();
        Cursor cursor = db.query(TABLE_NAME, null, COL_3 + "=?", new String[] {id}, null, null, null);
        String result = "";

        while(cursor.moveToNext()){
            result = cursor.getString(cursor.getColumnIndex(COL_4));
        }

        return result;
    }

    public long deleteUser(UserBean bean){
        SQLiteDatabase db = getWritableDatabase();
        String serial = String.valueOf(bean.getSerialNumber());

        return db.delete(TABLE_NAME, COL_0 + "=?", new String[] {serial});
    }
}

SQLite를 이용하면서, rowQurey와 SQLiteDatabase에서 제공하는 query를 섞어서 사용했다. 그냥 이것저것 다양한 방법을 사용해보고 싶었다.

 

후기

사실 프로젝트를 진행하면서 코드의 퀄리티보다는 완성에만 집중했었던 것 같다. 때문에 RecyclerView가 필요없는 부분에서도 써놓았던 코드를 Ctrl + C, V 하여 작성하는 참사가 발생했었다. 지금에야 GridView를 사용하는 방법으로 리사이클링을 했지만, 지금 보면 왜 그랬었을까 싶다. 그래도 개발에 대해서 많은 생각을 키울 수 있었던 프로젝트였다.

 

 

개발기간 : 2019.09.24. ~ 2019.10.22.

https://github.com/gurdlwl/Android_ShoppingMall

 

gurdlwl/Android_ShoppingMall

android 쇼핑몰. Contribute to gurdlwl/Android_ShoppingMall development by creating an account on GitHub.

github.com

'프로젝트 > 개인' 카테고리의 다른 글

[Python] Face Finder  (0) 2019.06.26
[Android] 내 손안에 장생포 재개발  (2) 2019.06.21
[C#] 한솥 매장 계산대 Project  (0) 2019.04.24
[Web] Apple Homepage Benchmarking Project  (0) 2019.04.23
댓글
공지사항