import 'dart:async';

import 'package:flutter/material.dart';
import 'package:sensors_plus/sensors_plus.dart'; // Import the sensors package

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.green,
      ),
      debugShowCheckedModeBanner: false,
      home: SensorExample(),
    );
  }
}

class SensorExample extends StatefulWidget {
  @override
  _SensorExampleStats createState() => _SensorExampleStats();
}

class _SensorExampleStats extends State<SensorExample> {
  double _gyroX = 0.0;
  double _gyroY = 0.0;
  double _gyroZ = 0.0;

  double _accelX = 0.0;
  double _accelY = 0.0;
  double _accelZ = 0.0;

  double _barometerEvent = 0.0;

  double _magnX = 0.0;
  double _magnY = 0.0;
  double _magnZ = 0.0;
  @override
  void initState() {
    super.initState();

    barometerEventStream().listen((event) {
      setState(() {
        _barometerEvent = event.pressure;
      });
    });
    magnetometerEventStream().listen((event){
      setState(() {
        _magnX = event.x;
        _magnY = event.y;
        _magnZ = event.z;
      });
    });

    gyroscopeEventStream().listen((GyroscopeEvent event) {
      setState(() {
        _gyroX = event.x;
        _gyroY = event.y;
        _gyroZ = event.z;
      });
    });

    accelerometerEventStream().listen((event) {
      setState(() {
        _accelX = event.x;
        _accelY = event.y;
        _accelZ = event.z;
      });
    });
  }
  @override
  void dispose() {
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Sensor Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Gyroscope Data:'),
            Text('gryoX: ${_gyroX.toStringAsFixed(2)}'),
            Text('gyroY: ${_gyroY.toStringAsFixed(2)}'),
            Text('gryoZ: ${_gyroZ.toStringAsFixed(2)}'),

            Text('Accelerometer Data:'),
            Text('accelX: ${_accelX.toStringAsFixed((2))}'),
            Text('accelY: ${_accelY.toStringAsFixed((2))}'),
            Text('accelZ: ${_accelZ.toStringAsFixed((2))}'),

            Text('Barometer Data:'),
            Text('baroPressure: ${_barometerEvent.toStringAsFixed(2)}'),

            Text('Magnetometer Data:'),
            Text('magnX: ${_magnX.toStringAsFixed(2)}'),
            Text('magnY: ${_magnY.toStringAsFixed(2)}'),
            Text('magnZ: ${_magnZ.toStringAsFixed(2)}'),
          ],
        ),
      ),
    );
  }
}

flutter/dart에서 사용가능한 패키지 중 하나인 sensors_plus에 있는 가속도계, 기압계, 자이로스코프, 자력계를 화면에 표시하는 방법

info.plist에서 http 접근을 허용할 수 있으나 권장되지 않는 방법이라고 한다.

권장하는 방법은  특정 사이트만 허용하는 방법이라고 하므로 이에 대해 작성한다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CADisableMinimumFrameDurationOnPhone</key>
	<true/>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleDisplayName</key>
	<string>간편지연증명서 출력</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>traindelaycertificateapp</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>$(FLUTTER_BUILD_NAME)</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>$(FLUTTER_BUILD_NUMBER)</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>UIApplicationSupportsIndirectInputEvents</key>
	<true/>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UISupportedInterfaceOrientations~ipad</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>GADApplicationIdentifier</key>
	<string>your app admob id</string>
	
	<!-- 여기부터 ATS 설정 추가 -->
	<key>NSAppTransportSecurity</key>
	<dict>
		<!-- 전체적으로는 ATS 적용 (false: 임의 로드는 허용하지 않음) -->
		<key>NSAllowsArbitraryLoads</key>
		<false/>

		<!-- 예외 도메인들 -->
		<key>NSExceptionDomains</key>
		<dict>
			<!-- info.siteA.com 예외 설정 -->
			<key>info.siteA.com</key>
			<dict>
				<key>NSIncludesSubdomains</key>
				<true/>
				<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
				<true/>
			</dict>

			<!-- www.siteB.co.kr 예외 설정 -->
			<key>www.siteB.co.kr</key>
			<dict>
				<key>NSIncludesSubdomains</key>
				<true/>
				<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
				<true/>
			</dict>
		</dict>
	</dict>
</dict>
</plist>

'' 카테고리의 다른 글

플레이 콘솔 임시 버전 삭제 방법  (1) 2024.12.02


'Web' 카테고리의 다른 글

Nexacro Dataset CRUD 정리  (0) 2020.06.18
Nexacro Components 제어 Function  (0) 2020.06.18
Nexacro Div 정렬 Function  (0) 2020.06.18
class AppBannerAdManager {
  BannerAd? _bannerAd;
  bool _isAdLoaded = false;

  VoidCallback? onAdLoadedCallback;

  AppBannerAdManager({this.onAdLoadedCallback});

  void loadBannerAd() {
    _bannerAd = BannerAd(
      adUnitId: 'ca-app-pub-3940256099942544/9214589741', // 실제 광고 단위 ID로 변경
      size: AdSize.banner,
      request: AdRequest(),
      listener: BannerAdListener(
        onAdLoaded: (Ad ad) {
          _isAdLoaded = true;
          print('배너 광고 로드 성공');
          // 광고 로드 시 상태 업데이트
          // 이때 setState를 호출하려면 해당 클래스가 StatefulWidget이어야 합니다.
          // 하지만 AppBannerAdManager는 그렇지 않으므로, 콜백으로 상태 업데이트를 전달해야 합니다.
          onAdLoadedCallback?.call();
        },

        onAdFailedToLoad: (Ad ad, LoadAdError error) {
          ad.dispose();
          print('배너 광고 로드 실패: $error');
        },
      ),
    );

    _bannerAd!.load();
  }

광고 로드 및 리소스 해제 기능을 제공하는 class

BannerAd? _BannerAd: google_mobile_ads 패키지에 있는 객체. ?는 null일 수 있음을 의미한다.

VoidCallback? onAdLoadedCallback: 광고가 로드되었을 때 호출될 콜백 함수.

AppBannerAdManager({this.onAdLoadedCallback}): 생성자. onAdLoadedCallback을 배개변수로 받음.

void loadBannerAd(): 배너 광고를 초기화하고 로드하는 역할

_bannerAd = BannerAd(...) : 새로운 BannerAd 인스턴스 생성

listenr: BannerAdListener: 광고 로드 상태를 모니터링하는 리스너

onAdLoaded: 광고가 정상적으로 로드됐을 경우. onAdLoadedCallback을 호출. ApBannerAdManager는 StatefulWidget이 아니므로 setState가 아니라 callback을 통해 상태 업데이트를 전달한다.

onAdFailedToLoad: 로드 실패했을 때, dispose로 리소스를 해제한다.

_bannerAd!.load(): 광고를 실제로 로드하는 method. 여기서 로드에 성공하면 listener 처리가 시작된다.

double? get bannerHeight => _isAdLoaded?_bannerAd?.size.height.toDouble() : 0: 광고가 로드되면 광고 높이 확인

Widget getBannerWidget(): 배너 광고가 로드된 경우 광고를 표시하는 위젯을 반환. 로드 실패 시, 빈 위젯 반환

Container: 광고를 감싸는 컨테이너 위젯

child: AdWidget(ad: _bannerAd!): 광고 객체를 전달받아서 표시. Container안에 bannerAd를 child로 두고 표시.

SizedBox.shrink(): 빈 공간을 차지하지 않는 위젯

 

class AppOpenAdManager {
  AppOpenAd? _appOpenAd;
  bool _isShowingAd = false;

  void loadAd() {
    AppOpenAd.load(
      adUnitId: 'ca-app-pub-3940256099942544/3419835294', // 실제 광고 단위 ID로 변경하세요
      request: const AdRequest(),
      adLoadCallback: AppOpenAdLoadCallback(
        onAdLoaded: (ad) {
          _appOpenAd = ad;
          debugPrint('앱 오프닝 광고 로드 성공');
          showAdIfAvailable();
        },
        onAdFailedToLoad: (error) {
          debugPrint('앱 오프닝 광고 로드 실패: $error');
        },
      ),
      //orientation: Orientation.portrait,
    );
  }

  void showAdIfAvailable() {
    if (_isShowingAd || _appOpenAd == null) {
      debugPrint('광고를 표시할 수 없습니다.');
      return;
    }

    _appOpenAd!.fullScreenContentCallback = FullScreenContentCallback(
      onAdShowedFullScreenContent: (ad) {
        _isShowingAd = true;
        debugPrint('앱 오프닝 광고가 표시되었습니다.');
      },
      onAdDismissedFullScreenContent: (ad) {
        _isShowingAd = false;
        _appOpenAd = null;
        debugPrint('앱 오프닝 광고가 닫혔습니다.');
        loadAd(); // 광고를 다시 로드
      },
      onAdFailedToShowFullScreenContent: (ad, error) {
        _isShowingAd = false;
        _appOpenAd = null;
        debugPrint('앱 오프닝 광고 표시 실패: $error');
        loadAd(); // 광고를 다시 로드
      },
    );

    _appOpenAd!.show();
  }
}

AppOpenAd: google_mobile_ads 패키지에 있는 객체

ad 객체에 대한 설명은 -5 문서 참고.

void showAdIfAvaliable: 광고가 로드되었고 광고가 표시되어있지 않은 경우 광고를 화면에 표시

_appOpenAd!.fullScreenContentCallback = FullScreenContentCallback(...)

FullScreenContentCallback : 광고 전체 화면 콘텐츠 상태 변화를 처리하는 콜백 클래스

onAdShowedFullScreenContent: 광고가 전체 화면으로 표시되었을 때 호출됨

onAdDismissedFullScreenContent : 광고가 닫혔을 때 호출됨

onAdFailedToShowFullScreenContent: 광고를 표시하는데 실패했을 때 호출됨

 

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  final AppOpenAdManager _appOpenAdManager = AppOpenAdManager();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _appOpenAdManager.loadAd();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      _appOpenAdManager.showAdIfAvailable(); // 앱이 재개될 때 광고를 표시
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Application Title',
      theme: ThemeData(
        primaryColor: Colors.indigo,
        colorScheme: ColorScheme.fromSwatch(
          primarySwatch: Colors.indigo,
          accentColor: Colors.amber,
        ),
        textTheme: const TextTheme(
          bodyLarge: TextStyle(fontSize: 16.0, color: Colors.black87),
          bodyMedium: TextStyle(fontSize: 14.0, color: Colors.black54),
          titleLarge: TextStyle(fontSize: 22.0, fontWeight: FontWeight.bold, color: Colors.indigo),
        ),
        buttonTheme: ButtonThemeData(
          buttonColor: Colors.amber,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(20.0),
          ),
        ),
      ),
      home: const LinkListPage(),
    );
  }
}

_MyAppState는 State 클래스를 상속받아서 MyApp 위젯의 상태를 관리하는 클래스이다. 

with WidgetsBindingObserver: WidgetsBindingObserver 믹스인을 사용하여 앱의 라이플사이클을 관찰할 수 있다고 한다. 뭔말인지 모르겠다. _MyAppState에서 또 호출하는 위젯들의 상태를 관찰하고 setState를 할 수 있다는 의미인건지 확인이 필요하다.

AppOpenAdManager는 오픈형 앱 광고 인스턴스 생성하는 줄이므로 이번 글에선 생략

void Initiate(): 상태가 초기화될때 호출되는 method.

super.initiate(): 부모(State)의 initiate를 호출하여 기본 초기화 작업 수행

WidgetsBinding.instance.addObserver(this): _MyAppState 클래스를 옵저버로 등록하여 앱의 라이프사이클을 관찰할 수 있게 한다.

_appOpenAdManager.loadAd() 도 광고 관련이므로 이번 글에선 생략

dispose() : 상태가 제거될 때 호출되는 method. 상태가 제거된다는게 어떤 의미인지 확인이 필요하다. _MyAppState는 MyApp 바로 다음의 클래스니까 앱이 꺼지는 상태를 말하는 것일까?

WidgetsBinding.instance.removeObserver(this): 옵저버 목록에서 _MyAppState를 제거. 더이상 앱상태를 관찰하지 못함.

super.dispose(): State의 dispose 호출,

didChangeAppLifecycleState(): 앱의 라이프사이클 상태가 변경될 때 호출되는 method.

AppLifecycleState.resumed: 앱이 백그라운드에서 포그라운드로 전환될 때의 상태라고 한다. flutter 앱 라이프사이클은 별도로 볼 필요가 있다.

_appOpenAdManager.showAdIfAvailable(): 생략

Widget build() : 실제 UI를 구성하는 method. 각 프레임(화면)마다 호출이 되어 위젯 트리(오브젝트를 트리 형태로 구성)를 구성한다.

return MaterialApp: MaterialApp 생성자를 호출하여 새로운 MaterialApp 위젯을 생성.

title은 실제 화면에서는 안보이므로 무시해도 된다. 어디서 보이는지 모르겠다. 창 제목이라는데 일단 안보인다.

theme: ThemeData: 앱의 전반적인 테마를 설정한다.

primaryColor: 앱의 기본 색상

colorScheme: 앱의 색상 체계 fromSwatch는 스와치를 기반으로한 생상 체계를 생성하는 팩토리 생성자. 스와치가 뭔지는 별도로 찾아봐도 될 듯 하다.

textTheme: 텍스트 스타일 정의

buttonTheme: 버튼 기본 스타일 정의. 내부 디자인 설명은 굳이..알아야 할까 어차피 할때마다 다를텐데

home: construction LinkListPage(): 앱의 기본 홈 화면을 설정한다. LinkListPage는 사용자가 처음 보게되는 화면이다.

LinkLIstPage를 블로그에 올려도 될런지 모르겠다.

 

 

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:html/parser.dart' show parse;
import 'package:webview_flutter/webview_flutter.dart';
import 'package:printing/printing.dart';
import 'package:http/http.dart' as http;
import 'package:google_mobile_ads/google_mobile_ads.dart';

이번 프로젝트에서 사용된 package들.

dart:convert: JSON 인코딩/디코딩 및 Base64 인코딩을 위해 사용. 사용한 부분이 있는지 기억이 잘안난다. 아마 파싱할 때 같이 쓰인 것 같다. 나머지는 뭐... 이름 그대로다.

google_mobile_ads 는 애드몹을 위해서 사용하는 패키지.

http는 http 요청을 보내는 용도, printing은 html내용을 pdf로 변환하기 위한 용도. 

webview_flutter는 웹페이지를 앱 내에서 표시하는 패키지. html/parser는 파싱용. 많이 썼다.

void main(){
  WidgetsFlutterBinding.ensureInitialized();
  MobileAds.instance.initialize();
  runApp(const MyApp());
}

WidgetFlutterBinding.ensureInitialized(): flutter 엔진 초기화.앱을 실행하기 전에 필요한 바인딩을 설정한다. WidgetsFlutterBinding의 인스턴스가 초기화되어있는지 확인한다. 초기화되지 않았다면 새로운 인스턴스를 생성하고 호출한다.

flutter/widgets.dart 또는 flutter/material.dart에 있다고 하다는데 cupertino에서도 쓰는걸 보면 그냥 처음에 이거 없으면 안되는 것 같다. 이유불문 있어야 한다고 생각하자. void Main()이 있어야 하는 이유와 다를 바가 없는 듯.

MobileAds.instance.initialize(): 설명 많이 할 거 없고 구글 애드몹 SDK 초기화 및 인스턴스 초기화.

runApp(const MyApp());

주어진 위젯(MyApp())을 루트 위젯으로 설정하고, 화면에 렌더링한다.  const가 굳이 붙어야 하냐 하면 잘모르겠긴 하다. 불변 객체로 생성하기 위함이라는데 음... 조금 더 확인이 필요해보인다.

어쨌건 이를 통해서 void main()에는 처음 인스턴스 초기화 및 초기 위젯 설정 외에 다른 역할을 하지 않는다는 것을 알 수 있다.

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

  @override
  State<MyApp> createState() => _MyAppState();
}

MyApp을 위젯이라 하는 이유는 StatefulWidget을 상속받기 때문이다. StatefulWidget은 상태를 가질 수 있는 위젯이다. 상태가 변경될 때마다 위젯을 다시 빌드할 수 있어서 동적 UI를 구현하는데 적합하다고 한다. 대부분의 앱들은 사실상 StatefulWidget으로 만들어지지 않을까 싶다. 그럼 StatelessWidget의 장점이 무엇일지 찾아보는 것도 좋아보인다.

const MyApp({super.key}) : MyApp 클래스 생성자. const인 이유는 불변 객체로 생성하여 성능 최적화를 도모하기 위함이라고 한다.

super.key: Flutter에서 위젯을 식별하고 효율적으로 업데이트할 수 있도록 돕는 Key 객체를 설정한다고 한다. 뭔 말인지 잘 모르겠다. 흠..

createState(): _MyAppState 클래스의 새 인스턴스를 생성하여 반환한다. _MyAppState는 MyApp의 상태를 관리하며, 실제로 UI를 빌드하고 상호작용을 처리한다. 

_MyAppState는 State<MyApp>을 상속받아, MyApp 위젯의 상태를 정의하고 관리한다. _MyAppState 내에서 setState()가 발생할 때 위젯을 다시 그린다고 생각하면 되는 것 같다. 그래서 값을 변경하는 경우가 있을 때 보통 setState 안에 담아서 처리한다.

 

애드몹 앱ID를 info.plist에 등록했는지 확인해봅시다. 앱id는 안드로이드, 애플 별도로 받아야 하기 때문에 볃도로 앱을 하나 더 등록하고 받은 앱id를 사용해서 작성해야 합니다. 

 

<key>GADApplicationIdentifier</key>
<string>ca-app-pub-3940256099942544~1458002511</string>

해당 항목이 있는지 확인 꼭 해보도록 한다. 내 3일이 날아갔었다..

'flutter' 카테고리의 다른 글

flutter 앱 개발 소스 분석-3  (0) 2024.12.12
flutter 앱 개발 소스 분석-2  (0) 2024.12.11
flutter 앱 개발 소스 분석-1  (1) 2024.12.11
flutter build appbundle 오류  (0) 2024.11.26
flutter 1일차  (0) 2024.11.24

이걸 왜 못찾고 있었는지 모르겠다.

챗GPT한테 물어보니까 아무리 봐도 답이 안나와서 이거저거 만져보다 알았다.

여기서 버전 수정을 클릭한다.

 

여기서 임시 출시 삭제를 클릭한다.

기존에 자꾸 버전이 꼬여서 뭐가 안되나 했었는데 이걸로 해결이 됐다. 에휴..

'' 카테고리의 다른 글

ios 앱 http 허용 방법  (0) 2024.12.25

+ Recent posts