GreenpOfferwall forExtends (v3) SDK Flutter (AOS) 지원 가이드 - rnd-adforus/GreenpSDK_Android GitHub Wiki
Flutter GreenpOfferwall forExtends (v3) SDK Flutter 지원 가이드는 Native로 개발되어 있는 GreenpOfferwallforExtends SDK를 Flutter에 연동하는 개발 코드 작성 가이드 입니다.
Plugin 형태로 지원되지 않으니 유의하시기 바랍니다.
다음의 가이드를 준수한 샘플 프로젝트를 경로를 참고 하시여 개발에 적용하시길 바랍니다. [Sample Guide]
- Flutter 프로젝트가 build.gradle로 구성된 경우
- Flutter 프로젝트가 build.gradle.kts로 구성된 경우
- AndroidManifest.xml 설정
- Proguard 설정
- AppCompatTheme 오류 경우
- Flutter 채널 연결
- GreenpSdkManager 클래스 설명 (Helper 클래스 구현)
- SDK 초기화 위치 및 실행 방법 (위젯 페이지 방식)
- SDK 사용법 (void main 방식)
- 파라미터 콜백 설정
Flutter 프로젝트 파일의 android 모듈 하위 app 레벨의 build.gradle 파일에 아래의 내용을 추가합니다.
rootProject.allprojects {
repositories {
...
maven {
url "https://nexus.adforus.com/repository/greenp/"
}
maven {
url 'https://artifact.bytedance.com/repository/pangle/'
}
}
}
dependencies {
...
// 가이드 문서 내 최신버전 참고
implementation 'com.adforus.sdk:greenp_v3:3.4.2.0-UAD'
}
allprojects {
repositories {
google() // Google의 Maven 저장소 추가
mavenCentral() // 중앙 Maven 저장소 추가
maven {
setUrl(uri("https://nexus.adforus.com/repository/greenp/"))
}
maven { // Optional - 오퍼월 내 구글 광고를 사용하시려면 작성 해주세요!
setUrl(uri("https://artifact.bytedance.com/repository/pangle/"))
}
}
}
dependencies {
// 가이드 문서 내 최신버전 참고
..
implementation("com.adforus.sdk:greenp_v3:3.4.2.0-UAD")
..
}
개인 식별과 리워드 적립을 위해 아래의 권한이 필요합니다. 권한 설정이 되지 않는 경우 SDK를 사용할 수 없습니다.
<uses-permission android:name=“android.permission.ACCESS_NETWORK_STATE”/>
<uses-permission android:name=“com.google.android.gms.permission.AD_ID”/>
<!-- CS 첨부파일 등록을 위해 외부 파일 접근권한이 필요합니다. api level에 따라 선택해서 선언해주세요. -->
<!-- targetSdk 32 이하 -->
<uses-permission android:name=“android.permission.READ_EXTERNAL_STORAGE”/>
<!-- targetSdk 33 이상 -->
<uses-permission android:name=“android.permission.READ_MEDIA_IMAGES”/>
그린피 SDK 에서는 HTTPS 를 사용하고 있지 않습니다. 따라서 SDK의 AndroidManifest.xml 을 통해 아래의 내용을 포함하고 있습니다.
(그린피에서는 많은 광고사를 실시간으로 연동하고 있어 여러 도메인이 추가/삭제 될 수 있습니다. 가급적 모든 HTTP 트래픽의 허용을 권장드립니다.)
[AndroidManifest.xml]
<application
...
android:usesCleartextTraffic="true">
...
<application/>
앱의 보안을 위해 HTTP 프로토콜을 허용하지 않고자 하시는 경우 usesCleartextTraffic 을 false 로 변경 후 network_security_config.xml 에 아래의 도메인을 예외사항으로 추가해주세요. (허용이 필요한 도메인의 목록은 예고없이 추가/삭제 될 수 있습니다.)
[network_security_config.xml]
<domain includeSubdomains="true">greenp.kr</domain>
<domain includeSubdomains="true">decaffeine.net</domain>
<domain includeSubdomains="true">adboost.co.kr</domain>
구글 광고 호출을 위한 Application ID을 application 태그 내부에 작성하여주세요. (앱별 Application ID를 Adforus 운영팀에 문의해주세요.)
```xml
<application
android:label="greenpsample"
android:name="${applicationName}"
android:usesCleartextTraffic="true"
android:theme="@style/LaunchTheme"
android:icon="@mipmap/ic_launcher">
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/>
...
<application/>
-keep class com.adforus.sdk.greenp.v3.** { *; }
-dontwarn com.adforus.sdk.greenp.v3.**
-keep class com.adforus.sdk.adsu.** {*;}
-dontwarn com.adforus.sdk.adsu.**
# Retrofit 2.X
## https://square.github.io/retrofit/ ##
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
-keepclasseswithmembers class * {
@retrofit2.http.* <methods>;
}
buildTypes {
release {
...
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
file("proguard-rules.pro")
)
...
}
}
buildTypes {
release {
...
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
...
}
}
E/AndroidRuntime(17535): java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.example.greenpsample/
com.adforus.sdk.greenp.v3.ui.
view.activity.GreenpOfferwallActivity}:java.lang.IllegalStateException:
You need to use a Theme.AppCompat theme (or descendant) with this activity.
SDK를 초기화하고 OfferWall Activity를 시작한 후, 호환되지 않는 테마 오류로 인해 앱이 크래시날 수 있습니다. 이 문제를 해결하려면 styles.xml에서 'LaunchTheme'을 수정해야 합니다.
- 오류나는 테마:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> //⚠️ 기본 플러터 설정값
<!-- Hides the system bar (status bar and navigation bar) -->
<item name="android:windowFullscreen">true</item>
</style>
- AppCompat 수정후 테마:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="Theme.AppCompat.Light.NoActionBar"> // ✅ 수정후
<item name="android:windowFullscreen">true</item>
</style>
이 테마를 .MainActivity에 적용하면 여전히 앱 오류가 발생할 수 있습니다. AndroidManifest.xml의 태그에 이 테마를 적용해야 합니다.
android:theme="@style/LaunchTheme"
- ✅ 올바른 테마 적용
<application
android:label="greenpsample"
android:name="${applicationName}"
android:usesCleartextTraffic="true"
android:theme="@style/LaunchTheme" // ✅ application 에 적용
android:icon="@mipmap/ic_launcher">
- ❌ 잘못된 테마 적용
<application
android:label="greenpsample"
android:name="${applicationName}"
android:usesCleartextTraffic="true"
// ⬅️ application 에 적용하기
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/LaunchTheme" // ⚠️ 기본 플러터 설정값 제거후 application으로 이동하기
android:launchMode="singleTop"
android:taskAffinity=""
플러터 엔진 초기화 시에 채널을 설정합니다. Flutter 프로젝트 내부의 MainActivity에 GreenpFlutter 인스턴스를 생성하여주세요. 채널 연결에 필요한 parameter를 Greenp SDK로 전달합니다.
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.adforus.sdk.greenp.v3.flutter.GreenpFlutter;
import io.flutter.Log;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
public class MainActivity extends FlutterActivity {
private GreenpFlutter greenpFlutter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
greenpFlutter.setActivity(this);
}
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
greenpFlutter = new GreenpFlutter(flutterEngine);
}
}
이 Dart 파일은 Flutter에서 네이티브 SDK를 쉽게 초기화하고 오퍼월(Offerwall) 기능을 호출할 수 있도록 도와주는 SDK 헬퍼 클래스입니다. 이 클래스를 Dart 파일에 그대로 복사하여 붙여넣어 사용할 수 있습니다.
import 'package:flutter/services.dart';
enum GreenpSdkStatus { initializing, success, failedWithError }
class GreenpSdkManager {
GreenpSdkStatus _sdkStatus = GreenpSdkStatus.initializing;
static const String _sdkMethodChannel = "com.adforus.sdk/greenp_channel";
static const String _offerWallMethodName = "initOfferWall";
static const String _offerWallInvokeChannel = 'startOfferWallActivity';
static late final MethodChannel _platformViewChannel;
GreenpSdkManager() {
_platformViewChannel = const MethodChannel(_sdkMethodChannel);
}
bool get isInitialized => _sdkStatus == GreenpSdkStatus.success;
bool get hasError => _sdkStatus == GreenpSdkStatus.failedWithError;
GreenpSdkStatus get sdkStatus => _sdkStatus;
Future<void> initGreenpSDK({
required String appCode,
required String userId,
required Function() onSuccess,
required Function(dynamic) onFailed,
}) async {
final sdkInitializeOptions = {'appCode': appCode, 'userId': userId};
try {
if (_sdkStatus != GreenpSdkStatus.success) {
await _platformViewChannel.invokeMethod(
_offerWallMethodName,
sdkInitializeOptions,
);
_sdkStatus = GreenpSdkStatus.success;
onSuccess();
}
} catch (e) {
_sdkStatus = GreenpSdkStatus.failedWithError;
onFailed(e);
}
}
void navigateToOfferWall() {
_platformViewChannel.invokeMethod(_offerWallInvokeChannel);
}
}
_sdkHelper.initGreenpSDK(
appCode: "appCode",
userId: "user1234",
onSuccess: () {},
onFailed: (e) {},
);
- appCode와 userId를 사용하여 SDK를 초기화합니다.
- 성공하면 onSuccess() 콜백이 실행됩니다.
- 실패하면 onFailed(e) 콜백이 실행되며, _sdkStatus 값이 failedWithError로 변경됩니다.
※ 유저 구분값 생성 규칙
- 각각의 유저별 고유한 값을 이용해야 합니다.
- 개인정보 및 ADID는 사용할 수 없습니다. ( 암호화 후 사용 가능 )
- 한글, 특수문자, 공백은 반드시 URL 인코딩 후 사용하셔야 합니다.
void navigateToOfferWall() {
_platformViewChannel.invokeMethod(_offerWallInvokeChannel);
}
- 네이티브 SDK의 메서드를 호출하여 오퍼월 화면을 엽니다.
이 클래스에서 가장 중요한 값들은 다음과 같습니다. 이 값들이 변경되면 네이티브 SDK와의 연동이 제대로 이루어지지 않을 수 있습니다.
static const String _sdkMethodChannel = "com.adforus.sdk/greenp_channel";
- Flutter와 네이티브(Android/iOS) 코드가 통신하는 MethodChannel의 식별자입니다.
static const String _offerWallMethodName = "initOfferWall";
- SDK를 초기화할 때 네이티브 코드에서 실행할 메서드의 이름입니다.
- initGreenpSDK 메서드에서 이 값을 사용하여 네이티브의 initOfferWall 메서드를 호출합니다.
- 이 이름이 변경되면 SDK 초기화가 동작하지 않을 수 있습니다.
static const String _offerWallInvokeChannel = 'startOfferWallActivity';
- 오퍼월 화면을 실행하기 위해 네이티브 코드에서 실행할 메서드의 이름입니다
SDK 초기화는 initState() 내부에서 실행하는 것이 가장 좋습니다. 이렇게 하면 앱이 실행될 때 한 번만 호출되며, 초기화가 완료된 후 SDK를 사용할 준비가 됩니다
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GreenpSdkManager _sdkHelper = GreenpSdkManager();
String errorMessage = "";
@override
void initState() {
super.initState();
_initializeSdk();
}
Future<void> _initializeSdk() async {
await _sdkHelper.initGreenpSDK(
appCode: "ZBhFaS5kxE",
userId: "1234",
onSuccess: () {},
onFailed: (error) {
setState(() {
errorMessage = error.toString();
});
},
);
setState(() {});
}
...
}
- initGreenpSDK()는 Future를 반환하는 비동기 함수이므로, await을 사용하여 초기화가 완료될 때까지 기다려야 합니다.
- 초기화가 끝나기 전에 SDK 관련 메서드를 실행하면 오류가 발생할 수 있습니다
- 만약 초기화에 실패하면 onFailed 콜백을 통해 errorMessage를 업데이트하여 사용자에게 오류 정보를 제공할 수 있습니다
- GreenpSdkManager는 SDK 사용을 쉽게 하기 위해 제공된 헬퍼 클래스입니다.
- 만약 GreenpSdkManager가 정의되지 않았다면, 6단계(Helper 클래스 구현)에서 GreenpSdkManager를 직접 구현해야 합니다.
초기화 성공 이후 'startOfferWallActivity' 키 값으로 그린피 OfferWall을 게재 합니다.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.greenAccent,
centerTitle: true,
title: Text("GreenP Sdk Demo"),
elevation: 1,
),
body:
_sdkHelper.isInitialized
? readyStateWidget() // ✅ 초기화 성공 → 오퍼월 이동 버튼 표시
: _sdkHelper.hasError
? errorPlaceHolderWidget() // ❌ 초기화 실패 → 오류 메시지 표시
: loadingPlaceHolder(), // ⏳ 초기화 중 → 로딩 UI 표시
);
}
Widget readyStateWidget() {
return SizedBox(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text("초기화 성공\n오퍼월로 이동할 준비가 되었습니다", textAlign: TextAlign.center),
TextButton(
onPressed: () {
_sdkHelper.navigateToOfferWall(); // ✅ 오퍼월로 이동
},
child: Text("오퍼월로 이동하기"),
),
],
),
);
}
Widget errorPlaceHolderWidget() {
return SizedBox(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [Text(errorMessage, textAlign: TextAlign.center)],
),
);
}
Widget loadingPlaceHolder() {
return SizedBox(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(width: 120, child: LinearProgressIndicator()),
Container(margin: EdgeInsets.all(12), child: Text("SDK 초기화 중")),
],
),
);
}
이 Widget은 GreenP Offerwall SDK의 초기화 상태에 따라 UI를 동적으로 업데이트하는 예제입니다. SDK의 상태에 따라 3가지 UI 상태를 가집니다:
- 1️⃣ SDK 초기화 중 (loadingPlaceHolder())
- 2️⃣ SDK 초기화 실패 (errorPlaceHolderWidget())
- 3️⃣ SDK 초기화 성공 (readyStateWidget()) → 오퍼월 이동 버튼 활성화
sdkHelper.initGreenpSDK()는 네이티브 코드를 호출하여. Flutter에서 네이티브 코드와 통신할 때는 Platform Channel을 사용하며, 이는 비동기적으로 실행됩니다.
따라서, Flutter 엔진이 완전히 초기화되기 전에 네이티브 코드를 호출하면 오류가 발생할 수 있습니다. 이를 방지하기 위해 WidgetsFlutterBinding.ensureInitialized()를 호출하여 Flutter 엔진이 초기화된 후에 네이티브 코드가 실행되도록 보장해야 합니다
final GreenpSdkManager _sdkHelper = GreenpSdkManager();
void main() async {
WidgetsFlutterBinding.ensureInitialized(); // ✅ Flutter 엔진 초기화 보장
try {
await sdkHelper.initGreenpSDK(
appCode: "ZBhFaS5kxE",
userId: "1234",
onSuccess: () {},
onFailed: (error) {},
);
} catch (e) {
}
runApp(MaterialApp(home: MyHomePage()));
}
SDK 초기화(initGreenpSDK())는 네이티브 코드와 통신하는 과정에서 오류가 발생할 가능성이 있습니다. 예를 들어:
- 네트워크 연결 문제
- 잘못된 appCode 또는 userId
이런 경우 에러가 발생하면 앱이 강제 종료될 수도 있기 때문에, try-catch를 사용하여 오류가 발생해도 앱이 멈추지 않도록 보호해야 합니다
GreenpSdkManager 인스턴스를 전역 변수로 선언하는 주요 장점은 다음과 같습니다:
- 앱 어디서든 오퍼월(OfferWall) 호출 가능 void main()에서 SDK를 초기화하면, 앱 전역에서 sdkHelper를 사용하여 오퍼월을 호출할 수 있습니다.
- 반복적인 인스턴스 생성 방지
- 네이티브 호출을 일관되게 유지
TextButton(
onPressed: () {
if (sdkHelper.isInitialized) {
sdkHelper.navigateToOfferWall();
}
},
child: Text("오퍼월로 이동하기"),
),
앱 시작 속도 지연
- void main()에서 Future 함수로 초기화하는 경우, await을 사용하면 앱 부팅 시간이 길어질 수 있음.
- 특히 여러 개의 비동기 초기화 작업이 포함될 경우, runApp() 호출까지의 시간이 길어질 위험이 있음.
예).
final GreenpSdkManager _sdkHelper = GreenpSdkManager();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await myApiForUserData()
await initializingDataBase()
await preparingSharedPeferences()
await checkingAppIntegrity()
await checkingUserToken()
try {
await sdkHelper.initGreenpSDK(
appCode: "ZBhFaS5kxE",
userId: "1234",
onSuccess: () {},
onFailed: (error) {},
);
} catch (e) {
}
runApp(MaterialApp(home: MyHomePage()));
}
만약 main 메서드에서 초기화해야 한다면, unawaited를 사용할 수 있습니다. 하지만 이렇게 하면 SDK 초기화가 완료되지 않았을 수 있어, 내비게이션 버튼이 제대로 작동하지 않을 수 있습니다
final GreenpSdkManager _sdkHelper = GreenpSdkManager();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
unawaited(sdkHelper.initGreenpSDK(
appCode: "ZBhFaS5kxE",
userId: "1234",
onSuccess: () {},
onFailed: (error) {},));
runApp(MaterialApp(home: MyHomePage()));
}
광고 참여가 정상적으로 완료된 경우, 매체사에서 등록 하신 콜백 URL로 암호화키를 전송해 드립니다.
CallBack url : 매체사 URLMethod : GET or POST (기본은 GET 방식이나 요청시 POST 방식으로도 가능합니다.
Ad Parameter | Type | 설명 |
---|---|---|
ads_idx | int |
광고키 |
ads_name | string |
갬페인 타이틀 |
rwd_cost | int |
매체사에 지급되는 단가 |
app_uid | string |
매체사에 보낸 유저 구분 값 (UserID) |
gp_key | int |
전환 건에 대한 유니크 값 |
etc | int |
referrer 값. 매체용 추가 정보(매체 uniq 클릭값 등) etc 대신 원하는 파라미터로 변경가능 |