> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adrop.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 배너 광고

> Flutter 앱에서 배너 광고를 구현하는 방법을 안내합니다.

## 개요

배너 광고는 화면의 일부 영역에 표시되는 직사각형 광고입니다. `AdropBannerView` 위젯을 사용하여 손쉽게 구현할 수 있습니다.

### 주요 특징

* 화면 상단, 하단 또는 중간에 고정 배치 가능
* 이미지 및 동영상 광고 지원
* 콜백을 통한 광고 이벤트 처리
* 광고 메타데이터 제공 (크리에이티브 ID, 캠페인 ID 등)

<Note>
  개발 환경에서는 테스트 유닛 ID를 사용하세요: `PUBLIC_TEST_UNIT_ID_320_100`
</Note>

***

## AdropBannerView

### 생성자

```dart theme={null}
AdropBannerView({
  required String unitId,
  AdropBannerListener? listener,
})
```

**파라미터**

| 파라미터       | 타입                    | 필수 | 설명                   |
| ---------- | --------------------- | -- | -------------------- |
| `unitId`   | `String`              | Y  | 애드컨트롤 콘솔에서 생성한 유닛 ID |
| `listener` | `AdropBannerListener` | N  | 광고 이벤트 리스너           |

### 속성

| 속성             | 타입              | 설명           |
| -------------- | --------------- | ------------ |
| `creativeSize` | `CreativeSize?` | 광고 크리에이티브 크기 |
| `adSize`       | `Size?`         | 배너 뷰의 크기 설정  |

### 메서드

| 메서드         | 반환 타입          | 설명         |
| ----------- | -------------- | ---------- |
| `load()`    | `Future<void>` | 광고를 로드합니다  |
| `dispose()` | `Future<void>` | 리소스를 해제합니다 |

***

## 기본 사용법

```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';

class BannerExample extends StatefulWidget {
  const BannerExample({super.key});

  @override
  State<BannerExample> createState() => _BannerExampleState();
}

class _BannerExampleState extends State<BannerExample> {
  bool isLoaded = false;
  late AdropBannerView bannerView;

  @override
  void initState() {
    super.initState();

    bannerView = AdropBannerView(
      unitId: 'YOUR_UNIT_ID',
      listener: AdropBannerListener(
        onAdReceived: (unitId, metadata) {
          debugPrint('배너 광고 수신 성공: $unitId');
          setState(() {
            isLoaded = true;
          });
        },
        onAdClicked: (unitId, metadata) {
          debugPrint('배너 광고 클릭: $unitId');
        },
        onAdImpression: (unitId, metadata) {
          debugPrint('배너 광고 노출: $unitId');
        },
        onAdFailedToReceive: (unitId, errorCode) {
          debugPrint('배너 광고 수신 실패: $unitId, $errorCode');
          setState(() {
            isLoaded = false;
          });
        },
      ),
    );

    // 광고 로드
    bannerView.load();
  }

  @override
  void dispose() {
    bannerView.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('배너 광고 예제')),
      body: Column(
        children: [
          // 메인 콘텐츠
          Expanded(
            child: Center(
              child: const Text('메인 콘텐츠'),
            ),
          ),
          // 배너 광고
          if (isLoaded)
            SizedBox(
              width: MediaQuery.of(context).size.width,
              height: 80,
              child: bannerView,
            ),
        ],
      ),
    );
  }
}
```

***

## AdropBannerListener

배너 광고 이벤트를 처리하는 리스너입니다.

### 콜백 함수

```dart theme={null}
AdropBannerListener(
  onAdReceived: (String unitId, Map<String, dynamic>? metadata) {
    // 광고 수신 성공
  },
  onAdClicked: (String unitId, Map<String, dynamic>? metadata) {
    // 광고 클릭
  },
  onAdImpression: (String unitId, Map<String, dynamic>? metadata) {
    // 광고 노출
  },
  onAdFailedToReceive: (String unitId, AdropErrorCode errorCode) {
    // 광고 수신 실패
  },
  onAdVideoStart: (String unitId) {
    // 동영상 광고 재생 시작
  },
  onAdVideoEnd: (String unitId) {
    // 동영상 광고 재생 종료
  },
)
```

### 콜백 설명

| 콜백                    | 설명                |
| --------------------- | ----------------- |
| `onAdReceived`        | 광고 수신 성공 시 호출     |
| `onAdClicked`         | 광고 클릭 시 호출        |
| `onAdImpression`      | 광고 노출 시 호출        |
| `onAdFailedToReceive` | 광고 수신 실패 시 호출     |
| `onAdVideoStart`      | 동영상 광고 재생 시작 시 호출 |
| `onAdVideoEnd`        | 동영상 광고 재생 종료 시 호출 |

### 메타데이터

`onAdReceived`, `onAdClicked`, `onAdImpression` 콜백에서 메타데이터를 받을 수 있습니다.

```dart theme={null}
onAdReceived: (unitId, metadata) {
  debugPrint('크리에이티브 ID: ${metadata?['creativeId']}');
  debugPrint('트랜잭션 ID: ${metadata?['txId']}');
  debugPrint('캠페인 ID: ${metadata?['campaignId']}');
  debugPrint('목적지 URL: ${metadata?['destinationURL']}');
  debugPrint('브라우저 타겟: ${metadata?['browserTarget']}');
}
```

| 필드               | 타입       | 설명                               |
| ---------------- | -------- | -------------------------------- |
| `creativeId`     | `String` | 크리에이티브 ID                        |
| `txId`           | `String` | 트랜잭션 ID                          |
| `campaignId`     | `String` | 캠페인 ID                           |
| `destinationURL` | `String` | 목적지 URL                          |
| `browserTarget`  | `int?`   | 브라우저 타겟 (0: 외부 브라우저, 1: 인앱 브라우저) |

<Note>
  크리에이티브 크기는 메타데이터가 아닌 `bannerView.creativeSize?.width` 및 `bannerView.creativeSize?.height`로 접근합니다.
</Note>

***

## 광고 크기

배너 광고는 유닛에 설정한 크기에 맞춰 컨테이너 크기를 지정해야 합니다.

### 일반적인 배너 크기

| 크기        | 테스트 유닛 ID                     | 용도    |
| --------- | ----------------------------- | ----- |
| 320 x 50  | `PUBLIC_TEST_UNIT_ID_320_50`  | 소형 배너 |
| 320 x 100 | `PUBLIC_TEST_UNIT_ID_320_100` | 중형 배너 |

### 고정 크기 사용

```dart theme={null}
SizedBox(
  width: 320,
  height: 100,
  child: bannerView,
)
```

### 화면 너비에 맞추기

```dart theme={null}
SizedBox(
  width: MediaQuery.of(context).size.width,
  height: 80,
  child: bannerView,
)
```

### 크기 설정

`adSize` 속성을 사용하여 배너 뷰의 크기를 명시적으로 설정할 수 있습니다.

```dart theme={null}
@override
void initState() {
  super.initState();

  bannerView = AdropBannerView(
    unitId: 'YOUR_UNIT_ID',
    listener: AdropBannerListener(
      onAdReceived: (unitId, metadata) {
        setState(() {
          isLoaded = true;
        });
      },
    ),
  );

  // 컨텍스트가 준비된 후 크기 설정
  WidgetsBinding.instance.addPostFrameCallback((_) {
    bannerView.adSize = Size(
      MediaQuery.of(context).size.width,
      80,
    );
  });
}
```

***

## 에러 처리

광고 로드 실패 시 적절한 에러 처리를 구현하세요.

```dart theme={null}
class _BannerExampleState extends State<BannerExample> {
  bool isLoaded = false;
  AdropErrorCode? errorCode;
  late AdropBannerView bannerView;

  @override
  void initState() {
    super.initState();

    bannerView = AdropBannerView(
      unitId: 'YOUR_UNIT_ID',
      listener: AdropBannerListener(
        onAdReceived: (unitId, metadata) {
          setState(() {
            isLoaded = true;
            errorCode = null;
          });
        },
        onAdFailedToReceive: (unitId, error) {
          setState(() {
            isLoaded = false;
            errorCode = error;
          });
        },
      ),
    );

    bannerView.load();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        if (isLoaded)
          SizedBox(
            width: MediaQuery.of(context).size.width,
            height: 80,
            child: bannerView,
          )
        else if (errorCode != null)
          Text('광고 로드 실패: ${errorCode?.code}'),
      ],
    );
  }

  @override
  void dispose() {
    bannerView.dispose();
    super.dispose();
  }
}
```

***

## 모범 사례

### 1. 리소스 해제

배너 뷰가 더 이상 필요하지 않을 때 반드시 `dispose()`를 호출하세요.

```dart theme={null}
@override
void dispose() {
  bannerView.dispose();
  super.dispose();
}
```

### 2. 광고 새로고침

필요에 따라 광고를 새로고침할 수 있습니다.

```dart theme={null}
void refreshAd() {
  setState(() {
    isLoaded = false;
  });
  bannerView.load();
}
```

### 3. 조건부 렌더링

광고가 로드될 때까지 배너 영역을 숨기거나 플레이스홀더를 표시하세요.

```dart theme={null}
Widget buildBanner() {
  if (isLoaded) {
    return SizedBox(
      width: MediaQuery.of(context).size.width,
      height: 80,
      child: bannerView,
    );
  } else if (isLoading) {
    return const SizedBox(
      height: 80,
      child: Center(child: CircularProgressIndicator()),
    );
  } else {
    return const SizedBox.shrink();
  }
}
```

### 4. 여러 배너 관리

여러 배너를 사용할 때는 각각의 상태를 관리하세요.

```dart theme={null}
class _MultiBannerState extends State<MultiBanner> {
  late AdropBannerView topBanner;
  late AdropBannerView bottomBanner;
  bool isTopLoaded = false;
  bool isBottomLoaded = false;

  @override
  void initState() {
    super.initState();

    topBanner = AdropBannerView(
      unitId: 'TOP_UNIT_ID',
      listener: AdropBannerListener(
        onAdReceived: (_, __) => setState(() => isTopLoaded = true),
      ),
    );

    bottomBanner = AdropBannerView(
      unitId: 'BOTTOM_UNIT_ID',
      listener: AdropBannerListener(
        onAdReceived: (_, __) => setState(() => isBottomLoaded = true),
      ),
    );

    topBanner.load();
    bottomBanner.load();
  }

  @override
  void dispose() {
    topBanner.dispose();
    bottomBanner.dispose();
    super.dispose();
  }

  // ...
}
```

***

## 전체 예제

```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';

class BannerAdPage extends StatefulWidget {
  const BannerAdPage({super.key});

  @override
  State<BannerAdPage> createState() => _BannerAdPageState();
}

class _BannerAdPageState extends State<BannerAdPage> {
  static const double bannerHeight = 80;
  bool isLoaded = false;
  bool isLoading = false;
  AdropErrorCode? errorCode;
  late AdropBannerView bannerView;

  @override
  void initState() {
    super.initState();

    bannerView = AdropBannerView(
      unitId: 'PUBLIC_TEST_UNIT_ID_320_100', // 테스트 유닛 ID
      listener: AdropBannerListener(
        onAdReceived: (unitId, metadata) {
          debugPrint('광고 수신 성공');
          debugPrint('크리에이티브 ID: ${metadata?['creativeId']}');
          debugPrint('캠페인 ID: ${metadata?['campaignId']}');
          debugPrint('크리에이티브 크기: ${bannerView.creativeSize?.width}x${bannerView.creativeSize?.height}');
          setState(() {
            isLoaded = true;
            isLoading = false;
            errorCode = null;
          });
        },
        onAdClicked: (unitId, metadata) {
          debugPrint('광고 클릭: ${metadata?['destinationURL']}');
        },
        onAdImpression: (unitId, metadata) {
          debugPrint('광고 노출: ${metadata?['creativeId']}');
        },
        onAdFailedToReceive: (unitId, error) {
          debugPrint('광고 수신 실패: $error');
          setState(() {
            isLoaded = false;
            isLoading = false;
            errorCode = error;
          });
        },
      ),
    );

    WidgetsBinding.instance.addPostFrameCallback((_) {
      bannerView.adSize = Size(
        MediaQuery.of(context).size.width,
        bannerHeight,
      );
    });
  }

  void loadAd() {
    setState(() {
      isLoading = true;
      errorCode = null;
    });
    bannerView.load();
  }

  @override
  void dispose() {
    bannerView.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('배너 광고 예제'),
      ),
      body: SafeArea(
        child: Column(
          children: [
            // 로드 버튼
            Padding(
              padding: const EdgeInsets.all(16),
              child: ElevatedButton(
                onPressed: isLoading ? null : loadAd,
                child: Text(isLoading ? '로딩 중...' : '배너 광고 로드'),
              ),
            ),

            // 상태 표시
            if (errorCode != null)
              Padding(
                padding: const EdgeInsets.all(16),
                child: Text(
                  '에러: ${errorCode?.code}',
                  style: const TextStyle(color: Colors.red),
                ),
              ),

            // 메인 콘텐츠
            const Expanded(
              child: Center(
                child: Text('메인 콘텐츠 영역'),
              ),
            ),

            // 배너 광고
            Container(
              width: MediaQuery.of(context).size.width,
              height: bannerHeight,
              color: Colors.grey[200],
              child: isLoaded
                  ? bannerView
                  : isLoading
                      ? const Center(child: CircularProgressIndicator())
                      : const Center(child: Text('광고 영역')),
            ),
          ],
        ),
      ),
    );
  }
}
```

***

## 백필 광고

백필 광고가 활성화된 경우, 직광고가 없을 때 자동으로 백필 광고가 로드됩니다. 백필 광고 로드는 SDK에서 자동으로 처리됩니다. `backfillNoFill` 에러 코드를 사용하여 백필 관련 에러를 처리할 수 있습니다.

```dart theme={null}
bannerView = AdropBannerView(
  unitId: 'YOUR_UNIT_ID',
  listener: AdropBannerListener(
    onAdReceived: (unitId, metadata) {
      debugPrint('광고 로드됨');
      setState(() {
        isLoaded = true;
      });
    },
    onAdFailedToReceive: (unitId, errorCode) {
      switch (errorCode) {
        case AdropErrorCode.adNoFill:
          debugPrint('직광고 없음');
          break;
        case AdropErrorCode.backfillNoFill:
          debugPrint('백필 광고도 없습니다');
          // 광고 영역 숨김 처리
          break;
        default:
          debugPrint('광고 로드 실패: ${errorCode.code}');
      }
    },
  ),
);
```

<Note>
  배너 광고 메타데이터에는 `creativeId`, `txId`, `campaignId`, `destinationURL`, `browserTarget` 필드만 포함됩니다. 백필 광고는 메타데이터로 구분할 수 없습니다. `backfillNoFill` 에러 코드를 사용하여 백필 광고 실패를 감지할 수 있습니다.
</Note>

<Note>
  백필 광고를 사용하려면 네이티브 플랫폼에 백필 의존성을 추가해야 합니다. [시작하기](/ko/sdk/flutter/overview)를 참고하세요.
</Note>

***

## 다음 단계

<CardGroup cols={2}>
  <Card title="네이티브 광고" href="/ko/sdk/flutter/native">
    UI에 맞게 커스터마이징 가능한 네이티브 광고 구현하기
  </Card>

  <Card title="전면 광고" href="/ko/sdk/flutter/interstitial">
    화면 전체를 덮는 전면 광고 구현하기
  </Card>

  <Card title="팝업 광고" href="/ko/sdk/flutter/popup">
    팝업 형태로 표시되는 광고 구현하기
  </Card>

  <Card title="레퍼런스" href="/ko/sdk/flutter/reference">
    타입, 메서드, 에러 코드 참고하기
  </Card>
</CardGroup>
