콘텐츠 대표 이미지 - 안드로이드 개발, 화면 사이를 가로지르는 데이터 택배! 🚀 인텐트(Intent) 완벽 정복 가이드

안드로이드 개발, 화면 사이를 가로지르는 데이터 택배! 🚀 인텐트(Intent) 완벽 정복 가이드

자바(Java)로 배우는 안드로이드 화면 간 데이터 전달의 모든 것, A to Z

안녕, 미래의 앱 개발 마스터! 👋

코딩의 세계에 막 발을 들인 당신, 혹은 이미 몇 개의 앱을 만들어본 당신!
안드로이드 앱을 만들다 보면 이런 순간, 꼭 오지 않아?

"로그인 화면에서 입력한 아이디, 메인 화면에 'OOO님 환영합니다!' 라고 띄워주고 싶은데..."
"상품 목록에서 클릭한 아이템의 상세 정보를 다음 화면에 보여줘야 하는데..."
"설정 화면에서 변경한 값을 이전 화면에 바로 적용하고 싶은데..."

바로 이거! 화면과 화면 사이의 데이터 이동!
마치 우리가 친구에게 카톡으로 메시지를 보내거나, 온라인 쇼핑몰에서 주문한 물건이 택배로 우리 집까지 오는 것처럼, 앱의 화면들도 서로 정보를 주고받아야 해. 이게 안 되면 앱은 그저 따로 노는 섬들의 집합일 뿐이지. 🏝️

이 중요한 임무를 수행하는 멋진 녀석이 바로 오늘 우리가 탈탈 털어볼 주인공, 인텐트(Intent)야.

"인텐트... 들어는 봤는데... 뭔가 어렵고 복잡할 것 같아..." 라고 생각했다면, 걱정은 저 멀리 던져버려! 🚀
내가 지금부터 친구한테 설명해주듯, 세상에서 제일 쉽고 재미있게 인텐트의 모든 것을 알려줄게. 이 글을 다 읽고 나면, 당신은 화면 사이를 자유자재로 넘나들며 데이터를 배달하는 '데이터 택배 마스터'가 되어 있을 거야.

💡 이 글에서 우리가 함께 정복할 것들!

1. 인텐트(Intent)가 대체 뭔지, 그 정체 파헤치기
2. 가장 기본! 문자, 숫자 같은 간단한 데이터 주고받기
3. 레벨 업! 객체(Object) 데이터를 통째로 넘기는 두 가지 비법 (Serializable vs Parcelable)
4. 고수의 길! 데이터를 돌려받는 방법 (구 방식 vs 최신 방식 완벽 비교)
5. 실수 줄이는 꿀팁과 주의사항까지!

자, 그럼 이제부터 안드로이드 세상을 연결하는 '데이터 고속도로', 인텐트의 세계로 함께 떠나볼까? Let's Go!

1. 인텐트(Intent), 너 정체가 뭐니? 🤔

인텐트를 한마디로 정의하면 뭘까?
교과서적인 정의는 "컴포넌트 간에 작업을 요청하거나 정보를 전달하기 위한 메시지 객체"인데...
음, 너무 딱딱하지? 😅

우리끼리는 이렇게 생각하자. 인텐트는 '안드로이드 세상의 만능 심부름꾼' 또는 '똑똑한 택배기사'라고!

A 화면(Activity)이 B 화면에게 "이것 좀 해줘!" 또는 "이 데이터 좀 받아!" 라고 부탁할 때 사용하는 '요청서' 또는 '택배 송장'인 셈이야.

이 심부름꾼은 크게 두 가지 종류의 일을 해.

1) 명시적 인텐트 (Explicit Intent) - "콕 집어서 시키는 심부름"

이름 그대로 '명시적으로', 즉, 심부름을 시킬 대상을 정확하게 지정하는 방식이야.
"야, MainActivity! 너 지금 당장 DetailActivity 좀 띄워줘!" 라고 클래스 이름을 콕 집어서 명령하는 거지.

주로 우리가 만든 앱 내부에서 화면(Activity)을 전환할 때 99% 이 방식을 사용해.
마치 "옆집 철수한테 이 편지 좀 전해줘!" 라고 정확한 대상을 지정하는 것과 같아.


// MainActivity.java 에서 DetailActivity.java 를 호출할 때

// 1. 인텐트 생성! (출발지, 도착지)
Intent intent = new Intent(this, DetailActivity.class);

// 2. 심부름 시작!
startActivity(intent);

너무 간단하지? `new Intent(출발지.this, 도착지.class)` 이 공식만 기억하면 돼!

2) 암시적 인텐트 (Implicit Intent) - "알아서 잘하는 애한테 시키는 심부름"

이건 대상을 콕 집지 않고, "이런 일 할 수 있는 사람, 누구든 좀 해줘!" 라고 외치는 방식이야.
예를 들어, "웹사이트 주소(https://www.google.com)를 보여줄 수 있는 앱, 나와라!" 라고 인텐트를 보내면, 안드로이드 시스템이 사용자의 폰에 설치된 웹 브라우저(크롬, 삼성 인터넷 등)를 찾아서 연결해줘.

전화 걸기, 문자 보내기, 갤러리에서 사진 가져오기 등 다른 앱의 기능을 빌려 쓸 때 주로 사용돼.
"피자 배달시킬 사람!" 하고 외치면 여러 피자집 앱이 "저요!" 하고 손드는 것과 비슷하지. 🍕


// 웹 브라우저를 열고 싶을 때

// 1. "이런 액션(ACTION_VIEW)을 할 건데, 데이터는 이 URL(uri)이야!" 라고 인텐트 생성
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.jaenung.net"));

// 2. 심부름 시작! (이걸 처리할 수 있는 앱을 안드로이드가 찾아줌)
startActivity(intent);

오늘 우리가 집중할 내용은 바로 명시적 인텐트를 사용해서 내 앱 안의 화면들끼리 데이터를 주고받는 방법이야. 암시적 인텐트는 이런 게 있구나~ 하고 가볍게 알아두면 돼!

2. 기본기 다지기: 간단한 데이터 배달하기 📦

자, 이제 본격적으로 데이터 택배를 보내보자!
가장 기본이 되는 문자열(String), 숫자(int), 참/거짓(boolean) 같은 원시 타입(Primitive Type) 데이터를 전달하는 방법이야.

인텐트라는 택배 상자에 데이터를 담는 행위를 `putExtra()` 메소드를 사용해서 해.
이름 그대로 "추가 정보를 담는다"는 뜻이지.

`putExtra()`는 항상 (Key, Value), 즉 (이름표, 실제 데이터) 쌍으로 동작해.
택배 상자에 '받는 사람'이라는 이름표를 붙이고 '홍길동'이라고 쓰는 것과 똑같아. 받는 쪽에서는 '받는 사람' 이름표를 보고 내용물을 꺼내야겠지? 이 '이름표'가 바로 Key야. Key는 보통 문자열(String)로 만들어.

Step 1: 보내는 쪽 (MainActivity.java) - 택배 포장하기 🎁

`MainActivity`에서 `DetailActivity`로 아이디(String), 나이(int), 로그인 상태(boolean)를 보낸다고 상상해보자.

MainActivity.java

import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

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

        Button moveButton = findViewById(R.id.move_button);
        moveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 1. DetailActivity로 가는 인텐트(택배 송장) 생성
                Intent intent = new Intent(MainActivity.this, DetailActivity.class);

                // 2. 택배 상자에 데이터 담기 (putExtra)
                // (Key, Value) 형식으로!
                intent.putExtra("userID", "jaenung123"); // String 데이터
                intent.putExtra("userAge", 25); // int 데이터
                intent.putExtra("isLoggedIn", true); // boolean 데이터

                // 3. 택배 출발! (Activity 시작)
                startActivity(intent);
            }
        });
    }
}

⭐ 프로의 꿀팁: Key는 상수로 관리하자!

코드에서 "userID", "userAge" 처럼 문자열을 직접 쓰는 걸 '하드코딩'이라고 해.
이러면 보내는 쪽과 받는 쪽에서 오타가 날 확률이 아주 높아! ("userID"인데 "userId"라고 쓴다거나...)
이런 실수를 방지하기 위해 Key 값들을 별도의 클래스에 상수로 정의해서 사용하는 게 훨씬 안전하고 깔끔해.


// Constants.java (프로젝트 어디서든 접근 가능하게)
public class Constants {
    public static final String KEY_USER_ID = "userID";
    public static final String KEY_USER_AGE = "userAge";
    public static final String KEY_IS_LOGGED_IN = "isLoggedIn";
}

// 사용할 때
intent.putExtra(Constants.KEY_USER_ID, "jaenung123");

이렇게 하면 자동완성 기능도 쓸 수 있고, 오타 걱정도 끝! ✨

Step 2: 받는 쪽 (DetailActivity.java) - 택배 개봉하기 🎉

자, 이제 `DetailActivity`에 택배가 도착했어. 상자를 열고 내용물을 꺼내보자.
데이터를 받을 때는 `getIntent()` 메소드로 나에게 온 인텐트 객체를 먼저 얻어야 해.

그리고 보낼 때 `putExtra()`를 썼다면, 받을 때는 데이터 타입에 맞는 `get...Extra()` 메소드를 사용해.

  • `getStringExtra()`
  • `getIntExtra()`
  • `getBooleanExtra()`
  • `getDoubleExtra()`
  • ... 등등
DetailActivity.java

import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;

public class DetailActivity extends AppCompatActivity {

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

        TextView idTextView = findViewById(R.id.id_textview);
        TextView ageTextView = findViewById(R.id.age_textview);
        TextView statusTextView = findViewById(R.id.status_textview);

        // 1. 나에게 온 인텐트(택배)를 받는다.
        Intent intent = getIntent();

        // 2. Key를 사용해서 데이터 꺼내기
        // String, boolean은 그냥 꺼내면 되지만...
        String userID = intent.getStringExtra("userID");

        // 숫자나 boolean 타입은 '기본값(default value)'을 설정해줘야 해!
        // 혹시 데이터가 안 왔을 경우를 대비한 안전장치.
        int userAge = intent.getIntExtra("userAge", 0); // "userAge" Key로 데이터가 없으면 0을 반환
        boolean isLoggedIn = intent.getBooleanExtra("isLoggedIn", false); // 데이터가 없으면 false를 반환

        // 3. 받은 데이터 활용하기 (화면에 표시)
        idTextView.setText("아이디: " + userID);
        ageTextView.setText("나이: " + userAge);
        statusTextView.setText("로그인 상태: " + isLoggedIn);
    }
}

🚨 잠깐! 기본값(Default Value)은 왜 필요할까?

`getIntExtra()`, `getBooleanExtra()` 같은 메소드를 보면 두 번째 파라미터로 기본값을 넣어주게 되어있어. 이건 아주 중요한 안전장치야!
만약 `MainActivity`에서 실수로 `userAge` 데이터를 보내는 걸 깜빡했다면? `DetailActivity`에서 `getIntExtra("userAge", 0)`를 호출했을 때, 해당 Key의 값이 없으므로 기본값인 `0`을 반환해줘.

만약 기본값 설정이 없다면? 데이터가 없을 때 앱이 비정상적으로 종료(Crash)될 수 있어!
"혹시라도 데이터가 안 올 경우, 이 값으로 대신 써!" 라고 알려주는 보험 같은 존재라고 생각하면 돼. `getStringExtra()`는 데이터가 없으면 `null`을 반환하기 때문에 별도의 기본값 파라미터는 없어.

어때? 생각보다 훨씬 간단하지?
이것만 알아도 웬만한 간단한 데이터 전달은 모두 마스터한 거야! 🥳

Activity A (보내는 곳) Intent intent = new Intent(...); intent.putExtra("Key", "Value"); startActivity(intent); Intent Data 📦 데이터를 싣고 출발! Activity B (받는 곳) Intent intent = getIntent(); String data = intent.getStringExtra("Key"); // 데이터 활용!

3. 레벨 업! 복잡한 객체(Object) 데이터 통째로 넘기기 🚚

자, 이제 좀 더 난이도를 높여보자.
아이디, 나이, 이름, 이메일 등 여러 정보를 가진 '사용자(User)' 객체를 한 방에 넘기고 싶을 땐 어떻게 해야 할까?
`putExtra("name", user.name)`, `putExtra("age", user.age)`... 이렇게 하나씩 분해해서 보낼 수도 있겠지만, 데이터가 10개, 20개가 되면 너무 비효율적이잖아. 끔찍하지! 😱

이럴 때 필요한 게 바로 객체를 직렬화(Serialization)하는 기술이야.

'직렬화'라는 말이 좀 어렵게 들리지만, 별거 아니야.
메모리 위에 둥둥 떠다니는 객체를 분해해서, 전송 가능한 형태로(예: 일렬로 늘어선 바이트 데이터) 변환하는 과정을 말해. 이렇게 변환해야 인텐트라는 택배 상자에 실을 수 있거든. 받는 쪽에선 이 데이터를 다시 원래의 객체 형태로 조립(역직렬화)해서 사용하고.

안드로이드에서는 객체를 직렬화하는 대표적인 방법이 두 가지가 있어. 바로 Serializable과 Parcelable이야.
둘 다 중요하니까 꼭 집중해서 들어야 해!

방법 1: Serializable - 간편함의 제왕 (The Easy Way)

`Serializable`은 자바(Java)에서 기본적으로 제공하는 직렬화 방식이야.
사용법은 정말... 너무너무 쉬워. 그냥 클래스 선언부에 `implements Serializable` 한 줄만 추가해주면 끝!

마치 인스턴트 라면처럼, 복잡한 과정 없이 바로 쓸 수 있는 간편함이 최대 장점이지. 🍜

User.java (Serializable 구현)

import java.io.Serializable;

// 그냥 이거 한 줄만 추가하면 끝!
public class User implements Serializable {
    private String name;
    private int age;
    private String email;

    public User(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    // Getter and Setter ...
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getEmail() {
        return email;
    }
}

이제 이 `User` 객체를 인텐트에 담아서 보내고 받아보자.

MainActivity.java (보내는 쪽)

// ...
User user = new User("재능이", 30, "master@jaenung.net");

Intent intent = new Intent(MainActivity.this, DetailActivity.class);
// 객체를 보낼 땐 putExtra에 Serializable 타입으로!
intent.putExtra("userInfo", user); 
startActivity(intent);
// ...
DetailActivity.java (받는 쪽)

// ...
Intent intent = getIntent();
// 받을 땐 getSerializableExtra() 사용! 그리고 원래 타입으로 형변환(Casting) 필수!
User user = (User) intent.getSerializableExtra("userInfo");

if (user != null) {
    String name = user.getName();
    int age = user.getAge();
    // ... 이제 user 객체의 데이터를 마음껏 사용!
}
// ...

정말 간단하지? 하지만 세상에 공짜는 없는 법...
`Serializable`의 이 간편함 뒤에는 숨겨진 단점이 있어.

⚙️ Serializable의 내부 동작과 단점

`Serializable`은 리플렉션(Reflection)이라는 기술을 사용해. 리플렉션은 실행 중에 클래스의 구조(멤버 변수, 메소드 등)를 분석해서 처리하는 방식인데, 이게 상당히 느리고 비효율적이야. 컴퓨터가 "이 클래스에 뭐가 들어있더라..." 하고 일일이 뜯어보는 과정에서 불필요한 객체들이 많이 생성되고 메모리도 많이 사용하게 돼.

간단한 앱에서는 체감이 안 될 수 있지만, 복잡하고 성능이 중요한 앱에서는 이 작은 차이가 앱의 버벅임으로 이어질 수 있어. 그래서 안드로이드 팀은 `Serializable` 대신, 안드로이드에 최적화된 `Parcelable` 사용을 강력하게 권장하고 있어.

방법 2: Parcelable - 성능의 왕좌 (The Pro Way 😎)

`Parcelable`은 안드로이드 SDK에 포함된, 안드로이드 시스템에 맞게 특별히 설계된 직렬화 방식이야.
마치 장인이 한 땀 한 땀 정성껏 만든 수제 요리 같다고 할까? 👨‍🍳

`Serializable`처럼 자동으로 처리해주지 않아. 개발자가 직접 어떤 데이터를 어떻게 쓰고 읽을지 명시적으로 코드를 작성해줘야 해. 그래서 처음 보면 코드가 길고 복잡해 보여서 "으악!" 할 수도 있어.

하지만 걱정 마!
그 원리만 이해하면 복사/붙여넣기 수준으로 쉽게 구현할 수 있고, 안드로이드 스튜디오가 코드 자동 생성도 도와줘. 무엇보다 그 성능적인 이점이 어마어마하기 때문에, 안드로이드 개발자라면 반드시 `Parcelable`을 사용해야 해!

자, 아까 그 `User` 클래스를 이번엔 `Parcelable`로 만들어보자.

User.java (Parcelable 구현)

import android.os.Parcel;
import android.os.Parcelable;

public class User implements Parcelable {
    private String name;
    private int age;
    private String email;

    // 생성자
    public User(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    // --- Parcelable 구현을 위한 필수 코드 ---

    // 1. Parcel 객체에서 데이터를 읽어와서 멤버 변수에 할당하는 생성자
    protected User(Parcel in) {
        name = in.readString();
        age = in.readInt();
        email = in.readString();
    }

    // 2. 객체를 Parcel로 변환(직렬화)하는 메소드
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
        dest.writeString(email);
    }

    // 3. Parcelable 인터페이스의 내용을 설명. 보통 0을 반환.
    @Override
    public int describeContents() {
        return 0;
    }

    // 4. 가장 중요! Parcel로부터 객체를 복원(역직렬화)하는 CREATOR
    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in); // 위에서 정의한 생성자 호출
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };

    // --- Getter and Setter ---
    // ...
}

코드가 확 길어졌지? 하나씩 뜯어보자.

  • `writeToParcel(Parcel dest, int flags)`: 내 객체의 멤버 변수들을 `Parcel`이라는 꾸러미에 순서대로 써넣는(write) 역할을 해. `dest.writeString(name)`, `dest.writeInt(age)` 처럼. 순서가 매우 중요해!
  • `User(Parcel in)` 생성자: `writeToParcel`에서 썼던 순서 그대로 `Parcel` 꾸러미에서 데이터를 읽어와서(read) 멤버 변수를 채워주는 역할을 해. 쓰는 순서와 읽는 순서가 다르면 데이터가 엉망이 되겠지?
  • `CREATOR`: 안드로이드 시스템이 이 `CREATOR`를 보고 "아, 이 클래스는 이렇게 객체로 복원하면 되는구나!" 하고 알아차리는 이정표 같은 녀석이야. `createFromParcel()` 메소드가 핵심이고, 위에서 만든 생성자를 호출해서 객체를 생성해줘. 이건 그냥 이 형식 그대로 쓴다고 외워두면 돼.

보내고 받는 코드는 `Serializable`과 거의 비슷해. 메소드 이름만 살짝 달라.

MainActivity.java (보내는 쪽)

// ...
User user = new User("파슬러블", 30, "parcel@android.com");

Intent intent = new Intent(MainActivity.this, DetailActivity.class);
// 객체를 보낼 땐 putExtra에 Parcelable 타입으로!
intent.putExtra("userInfo", user); 
startActivity(intent);
// ...
DetailActivity.java (받는 쪽)

// ...
Intent intent = getIntent();
// 받을 땐 getParcelableExtra() 사용! 형변환은 여전히 필요.
User user = intent.getParcelableExtra("userInfo");

if (user != null) {
    // ... 똑같이 사용!
}
// ...

🚀 안드로이드 스튜디오 자동 생성 기능 활용하기!

`Parcelable` 코드를 직접 다 타이핑하는 건 너무 힘들어. 다행히 우리에겐 똑똑한 안드로이드 스튜디오가 있지!
클래스 이름(`User`)에 커서를 놓고 `Alt + Enter` (Mac: `Option + Return`) 키를 누르면 뜨는 메뉴에서 'Add Parcelable implementation'을 선택해봐. 그럼 필요한 코드를 마법처럼 뿅! 하고 만들어줘. 우리는 멤버 변수만 잘 선택해주면 돼.

Serializable vs Parcelable 최종 정리! 🥊

| 구분 | Serializable | Parcelable | | :--- | :--- | :--- | | **구현 난이도** | **최상 (매우 쉬움)** | 하 (코드가 길지만 자동생성 가능) | | **성능** | 느림 (리플렉션 사용) | **매우 빠름 (안드로이드 최적화)** | | **메모리 사용** | 많음 (불필요한 객체 생성) | **적음 (효율적)** | | **용도** | 간단한 데이터, 파일 저장 등 범용 | **안드로이드 컴포넌트 간 데이터 전달** | | **결론** | 편하지만 안드로이드에선 비추천 | **귀찮아도 무조건 이걸 쓰자! (The Android Way)** |

결론: 안드로이드 앱에서 화면 간에 객체를 전달할 때는, 고민하지 말고 Parcelable을 사용하자!

응용: 객체 리스트(ArrayList) 넘기기

실무에서는 객체 하나만 넘기는 경우보다, 여러 개의 객체가 담긴 리스트를 넘기는 경우가 훨씬 많아.
예를 들어, 사용자 목록, 상품 목록 같은 것들 말이야. 이것도 `Parcelable`을 이용하면 아주 간단해.

MainActivity.java (리스트 보내는 쪽)

ArrayList<User> userList = new ArrayList<>();
userList.add(new User("김철수", 28, "chul@test.com"));
userList.add(new User("이영희", 31, "young@test.com"));
userList.add(new User("박바둑", 25, "baduk@test.com"));

Intent intent = new Intent(MainActivity.this, ListActivity.class);
// 리스트를 보낼 땐 putParcelableArrayListExtra() 사용!
intent.putParcelableArrayListExtra("userList", userList);
startActivity(intent);
ListActivity.java (리스트 받는 쪽)

Intent intent = getIntent();
// 받을 땐 getParcelableArrayListExtra() 사용!
ArrayList<User> receivedList = intent.getParcelableArrayListExtra("userList");

if (receivedList != null && !receivedList.isEmpty()) {
    // 이제 리스트를 가지고 RecyclerView 어댑터에 넘기거나 등등...
    // for (User user : receivedList) { ... }
}

`User` 클래스가 `Parcelable`을 구현하고 있기만 하다면, 리스트를 보내고 받는 것도 이렇게나 쉬워!

4. 고수의 길: 데이터 돌려받기 (feat. Activity Result API) 🔄

지금까지는 A에서 B로 일방적으로 데이터를 보내기만 했어.
하지만 이런 경우는 어떨까?

"A(게시글 작성 화면)에서 B(갤러리 화면)를 띄워서 사진을 선택하게 한 뒤, 선택한 사진 정보를 다시 A로 가져와야 해."
"A(메인 화면)에서 B(설정 화면)를 띄워서 닉네임을 변경하고, 변경된 닉네임을 A 화면에 바로 반영해야 해."

이처럼 B에서 수행한 작업의 *결과*를 다시 A로 돌려받아야 할 때가 있어.
이것도 두 가지 방법이 있는데, 과거의 유물(?)이 된 방법과 현재의 표준이 된 방법이 있어. 둘 다 알아둬야 레거시 코드도 이해할 수 있겠지?

방법 1: `startActivityForResult()` - 과거의 방식 (The Old Way)

이름부터 '결과를 위한 액티비티 시작'이지? 이 방법은 세 가지 요소의 조합으로 동작해.

  1. `startActivityForResult(intent, requestCode)`: A에서 B를 시작할 때 그냥 `startActivity` 대신 이걸 사용해. `requestCode`는 내가 임의로 정하는 숫자인데, "내가 지금 보낸 요청이 몇 번 요청인지" 식별하는 번호표 같은 거야. (예: 1번 요청은 갤러리, 2번 요청은 설정...)
  2. `setResult(resultCode, intent)`: B에서 작업을 마치고 A로 돌아가기 직전에 호출해. "작업이 성공했는지/실패했는지(`resultCode`)"와 "돌려줄 데이터가 담긴 인텐트"를 설정하는 거야.
  3. `onActivityResult(requestCode, resultCode, data)`: B가 종료되고 A로 돌아오면, A의 이 메소드가 자동으로 호출돼. 여기서 `requestCode`를 보고 "아, 아까 보냈던 1번 요청의 결과구나!" 하고 확인한 뒤, `resultCode`와 `data`(B에서 보낸 인텐트)를 가지고 후속 작업을 처리해.

말로만 들으면 복잡하니까 코드를 보자.

MainActivity.java (A: 요청하고 결과 받는 곳)

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_EDIT_NICKNAME = 101; // 요청 코드 정의
    private TextView nicknameTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        nicknameTextView = findViewById(R.id.nickname_textview);
        Button editButton = findViewById(R.id.edit_button);

        editButton.setOnClickListener(v -> {
            Intent intent = new Intent(this, EditActivity.class);
            // 현재 닉네임을 EditActivity로 전달
            intent.putExtra("currentNickname", nicknameTextView.getText().toString());
            startActivityForResult(intent, REQUEST_CODE_EDIT_NICKNAME); // 결과를 받기 위해 호출
        });
    }

    // 결과를 받는 곳
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        // 1. 내가 보낸 요청이 맞는지 확인 (요청 코드로)
        if (requestCode == REQUEST_CODE_EDIT_NICKNAME) {
            // 2. 작업이 성공적으로 완료되었는지 확인 (결과 코드로)
            if (resultCode == RESULT_OK) {
                // 3. 돌아온 데이터가 있는지, null이 아닌지 확인
                if (data != null) {
                    String newNickname = data.getStringExtra("newNickname");
                    nicknameTextView.setText(newNickname);
                }
            }
        }
    }
}
EditActivity.java (B: 작업하고 결과 돌려주는 곳)

public class EditActivity extends AppCompatActivity {

    private EditText nicknameEditText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        nicknameEditText = findViewById(R.id.nickname_edittext);
        Button saveButton = findViewById(R.id.save_button);

        // MainActivity에서 보낸 현재 닉네임을 받아서 EditText에 세팅
        String currentNickname = getIntent().getStringExtra("currentNickname");
        nicknameEditText.setText(currentNickname);

        saveButton.setOnClickListener(v -> {
            Intent resultIntent = new Intent();
            // 돌려줄 데이터를 인텐트에 담는다
            resultIntent.putExtra("newNickname", nicknameEditText.getText().toString());
            
            // 결과 설정! (성공했다는 의미의 RESULT_OK와 데이터를 담은 인텐트)
            setResult(RESULT_OK, resultIntent);
            
            // 현재 액티비티 종료 (이전 화면으로 돌아감)
            finish();
        });
    }
}

🚨 Deprecated! 이 방식은 이제 그만!

`startActivityForResult`와 `onActivityResult` 방식은 오랫동안 사용되었지만, 몇 가지 큰 단점이 있어.
- 강한 결합도: `onActivityResult` 하나에서 모든 요청 결과를 `if`문으로 분기 처리해야 해서 코드가 복잡해지고 지저분해져.
- 생명주기 문제: 액티비티가 메모리 부족으로 종료되었다가 다시 생성될 때 결과 처리가 꼬일 수 있어.
- 타입 불안정성: `Intent`에서 데이터를 꺼낼 때 타입이 안전하게 보장되지 않아.

그래서 구글은 이 방식을 Deprecated(사용 자제) 처리하고, 훨씬 더 안전하고 깔끔한 새로운 방식을 만들었어. 그게 바로 다음에 배울 Activity Result API야!

방법 2: Activity Result API - 현재의 표준 (The New & Better Way) ✅

Activity Result API는 기존 방식의 단점을 모두 해결한, 현대적인 안드로이드 개발의 표준이야.
핵심은 '요청과 결과 처리를 분리하고, 콜백(Callback) 기반으로 동작한다'는 거야.

말이 좀 어렵지? 코드를 보면 바로 이해될 거야. "결과를 받으면 이 코드를 실행해줘!" 라고 미리 등록해두는 방식이라고 생각하면 돼.

핵심 요소는 `registerForActivityResult` 야.

MainActivity.java (A: 새로운 방식으로 요청하고 결과 받기)

public class MainActivity extends AppCompatActivity {

    private TextView nicknameTextView;

    // 1. ActivityResultLauncher를 멤버 변수로 선언
    // ActivityResultContracts.StartActivityForResult() : 기본 인텐트 실행 계약
    // 람다식(result -> ...) : 결과가 돌아왔을 때 실행될 콜백
    private final ActivityResultLauncher<Intent> editNicknameLauncher = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            result -> {
                // 결과가 돌아왔을 때 이 부분이 실행됨!
                if (result.getResultCode() == Activity.RESULT_OK) {
                    Intent data = result.getData();
                    if (data != null) {
                        String newNickname = data.getStringExtra("newNickname");
                        nicknameTextView.setText(newNickname);
                    }
                }
            });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        nicknameTextView = findViewById(R.id.nickname_textview);
        Button editButton = findViewById(R.id.edit_button);

        editButton.setOnClickListener(v -> {
            Intent intent = new Intent(this, EditActivity.class);
            intent.putExtra("currentNickname", nicknameTextView.getText().toString());
            
            // 2. startActivityForResult 대신, 위에서 만든 launcher의 launch() 메소드 호출
            editNicknameLauncher.launch(intent);
        });
    }
    
    // onActivityResult() 메소드는 이제 필요 없음! 깔끔!
}

`EditActivity.java` (B 화면) 코드는 이전 방식과 완전히 동일해! `setResult()`와 `finish()`를 쓰는 건 똑같아.
오직 A 화면의 코드만 바뀌는 거야.

어때? 코드가 훨씬 깔끔해졌지?

  • `onActivityResult`라는 거대한 메소드가 사라졌어.
  • `registerForActivityResult`를 통해 "닉네임 수정"이라는 특정 요청에 대한 결과 처리 로직이 명확하게 분리되었어.
  • 더 이상 `requestCode` 같은 숫자 상수를 관리할 필요가 없어!
  • 이 방식은 액티비티의 생명주기를 알아서 관리해주기 때문에 훨씬 안전해.

새로운 프로젝트를 하거나 기존 코드를 리팩토링할 기회가 있다면, 무조건 Activity Result API를 사용해야 해. 이게 바로 전문가로 나아가는 길이야! 재능 공유 플랫폼 **재능넷** 같은 곳에서 안드로이드 개발 관련 멘토링을 받을 때도 이런 최신 기술 트렌드를 잘 아는 것이 중요하겠지?

5. 이것만은 알고 가자! 꿀팁 & 주의사항 📝

자, 이제 당신은 인텐트를 거의 마스터했어!
마지막으로 실무에서 실수를 줄이고 더 견고한 코드를 작성하기 위한 몇 가지 팁과 주의사항을 알려줄게.

1. Key는 반드시 상수로 관리하라! (두 번 강조)

앞에서도 잠깐 언급했지만, 이건 아무리 강조해도 지나치지 않아.
`intent.putExtra("userId", ...)` 처럼 Key를 문자열로 직접 쓰는 건 정말 위험해. 받는 쪽에서 `intent.getStringExtra("userID")` 처럼 대소문자 하나만 틀려도 데이터는 `null`이 되고, 원인을 찾기 위해 한참을 헤매게 될 거야.

꼭 `public static final String KEY_USER_ID = "user_id";` 와 같이 상수로 만들어서 사용하자.

2. 인텐트에는 너무 큰 데이터를 싣지 마라! (TransactionTooLargeException)

인텐트는 간단한 데이터를 빠르게 전달하기 위해 설계되었어. 택배로 치면 서류 봉투나 작은 소포 정도랄까?
여기에 거대한 이미지 비트맵(Bitmap)이나 아주 긴 동영상 파일 경로 리스트 같은 수십 MB짜리 데이터를 실으려고 하면 어떻게 될까?

바로 `TransactionTooLargeException`이라는 무시무시한 예외가 발생하면서 앱이 죽어버려! 💣
안드로이드 시스템에서 프로세스 간 데이터 전송(IPC)을 위한 버퍼 크기는 약 1MB로 제한되어 있기 때문이야.

큰 데이터는 인텐트로 직접 전달하는 게 아니야. 대신 아래와 같은 방법을 사용해야 해.

  • 파일로 저장 후 경로 전달: 이미지를 파일로 저장하고, 인텐트에는 그 파일의 경로(URI)만 전달해서 받는 쪽에서 파일을 읽어오게 하는 방법.
  • 데이터베이스 활용: 데이터를 DB(SQLite, Room 등)에 저장하고, 인텐트에는 해당 데이터의 ID 값만 넘겨서 받는 쪽에서 ID로 조회하게 하는 방법.
  • Singleton 패턴 또는 Static 변수: 앱 전역에서 접근 가능한 공간에 데이터를 잠시 보관하는 방법. (주의해서 사용해야 함)

3. 데이터는 항상 '없을 수도 있다'고 가정하라! (Null Check)

"당연히 데이터가 넘어오겠지?" 라는 생각은 금물이야.
예상치 못한 로직의 변경이나 버그로 인해 인텐트에 데이터가 담기지 않은 채로 다음 화면이 열릴 수도 있어.

데이터를 받을 때는 항상 null 체크를 하는 습관을 들이자.


// Bad 👎
String name = getIntent().getStringExtra("name");
textView.setText(name.toUpperCase()); // name이 null이면 여기서 NullPointerException 발생!

// Good 👍
String name = getIntent().getStringExtra("name");
if (name != null && !name.isEmpty()) {
    textView.setText(name.toUpperCase());
} else {
    // 이름이 없는 경우의 처리 (예: "이름 없음" 표시)
    textView.setText("이름 없음");
}

이런 방어적인 코딩 습관이 당신의 앱을 더욱 안정적으로 만들어 줄 거야.

4. Bundle: 인텐트 데이터의 진짜 저장소

사실 우리가 `intent.putExtra("key", value)`를 사용할 때, 데이터는 인텐트 내부에 있는 `Bundle`이라는 객체에 저장돼. `Bundle`은 다양한 타입의 값을 Key-Value 형태로 저장할 수 있는 만능 해시맵(HashMap) 같은 녀석이야.

`putExtra`는 이 `Bundle`에 데이터를 쉽게 넣을 수 있도록 도와주는 편의 메소드일 뿐이지.
아래 두 코드는 사실상 똑같이 동작해.


// 방법 1: putExtra 사용
intent.putExtra("name", "홍길동");
intent.putExtra("age", 20);

// 방법 2: Bundle 사용
Bundle bundle = new Bundle();
bundle.putString("name", "홍길동");
bundle.putInt("age", 20);
intent.putExtras(bundle);

`Bundle`은 화면 회전 시 데이터를 유지하는 `onSaveInstanceState` 등 안드로이드 시스템 곳곳에서 데이터를 임시 저장하는 용도로 널리 쓰이니까, "아, 인텐트의 데이터는 번들이라는 곳에 담기는구나" 하고 알아두면 좋아!

마치며: 이제 당신은 데이터 배달의 프로! 🏆

와, 정말 긴 여정이었어! 여기까지 따라온 당신, 정말 대단해! 👍
이제 당신은 더 이상 화면 간 데이터 전달을 두려워할 필요가 없어.

오늘 우리는 안드로이드의 심부름꾼 인텐트(Intent)의 정체부터 시작해서,

  • 간단한 데이터를 `putExtra`로 주고받는 기본기,
  • `Serializable`과 `Parcelable`을 이용해 복잡한 객체를 통째로 넘기는 고급 기술,
  • 그리고 `Activity Result API`를 사용해 세련되게 결과를 돌려받는 최신 트렌드까지.

이 모든 것을 마스터했어. 이 지식은 당신이 앞으로 만들 모든 안드로이드 앱의 뼈대가 되어줄 거야.
화면들이 서로 유기적으로 소통하고 데이터를 주고받는, 살아 숨 쉬는 앱을 만들 수 있는 강력한 무기를 얻은 셈이지.

기억해. 코딩은 지식을 머리에 넣는 것에서 끝나지 않아.
지금 당장 안드로이드 스튜디오를 켜고, 오늘 배운 내용으로 작은 샘플 앱이라도 직접 만들어봐. 데이터를 보내고, 받고, 객체를 `Parcelable`로 만들어보고, 결과를 돌려받는 과정을 직접 손으로 경험하는 것이 당신의 실력을 가장 빠르게 성장시키는 길이야.

개발의 길은 때로는 어렵고 외롭게 느껴질 수 있지만, 이렇게 하나씩 지식을 정복해나가는 즐거움이 우리를 계속 나아가게 하는 원동력이지. 만약 더 깊은 지식이나 프로젝트 경험이 필요하다면, **재능넷**과 같은 플랫폼에서 활동하는 현업 개발자들의 도움을 받는 것도 좋은 방법일 거야.

자, 이제 당신의 손으로 멋진 앱을 만들어 세상을 놀라게 해줘!
언제나 당신의 코딩 여정을 응원할게! 화이팅! 🎉

댓글 작성

이 글에 대한 여러분의 생각을 들려주세요

댓글 0
댓글을 불러오는 중입니다.

아직 댓글이 없습니다.