728x90

처음 플러터를 알게 된 건 2020년 봄 쯤이였다.

정확히 어떤 경로로 처음 알게 되었는지는 생각나지 않지만 첫 인상 부터 임팩트 있었다.

그때는 사이드 프로젝트로 시작한 서버 API 설계 삽질에 땀을 삐질삐질 흘리고 있던 시기라 바로 배우지 못했다.

 

여담이지만,

액션스크립트(AS3.0)를 이용해서 개발을 오래 해오던 친한 형이

회사 사정상 퇴사를 하게 되었고 1인 개발자로 전향을 준비하고 있었다.

 

AS 3.0은 Adobe Flash 기반으로 한 크로스 플랫폼을 이다.

그 형에게 잘 맞을 것 같아 추천해 주었고, 지금까지 잘 사용하고 계신다. ( 뿌듯 😅 )

 


올해 4월로 기억한다.

유튜브에서 추천영상으로 꼬북좌(브레이브걸스 유정)을 처음 봤던게.

 

어떻게 저렇게 "찐텐"으로 웃으며 공연할 수 있을까 하는 생각에 덩달아 기분이 좋아졌다.

아이돌 노래를 좋아하긴 커녕 싫어하는 쪽에 가까운 나였지만, 브레이브걸스 만큼은 눈길이 갔다.

무명과 같았던 어려운 시절의 개인 인스타 영상을 보면서, 점점 찐팬이 되어 갔던 것 같다.

 

브레이브걸스를 응원하기 위해 내가 할 수 있는게 뭘까 생각 하던중,

화력이 뜨겁기로 소문난 디시인사이드(dcinside.com) 브레이브 갤러리를 방문하게 되었다.

 

그 곳에선 밤마다 경매(?)가 열리고 있었다.

 

댓글엔 50 ... 60... 100 .. 250... 250낙찰...

뭐지??

 

한정판 사인 앨범 같은 굿즈를 상품으로 경매를 해서

낙찰된 금액으로 도시락 조공이나, 앨범공구 등

브레이브걸스 이름으로 자선 단체 후원하기 같은 것들을 하게된다.

 

대다수의 팬층이 30~40대 직장인이라서 낮이나 이른 저녁에 이런 경매가 열리게 되면

참여하고 싶어도 못하는 유저들이 많았다.

 

그들은 그것을 " 날치기 " 라고 부르고 있었다. 🤣

 

그래, 이거다.

 

경매글이 올라오면 모바일 어플로 노티메시지를 보내주고 링크를 걸어 해당글로 갈 수 있도록 만들자.

 

- 관련 영상

- 스케쥴

- 실시간 차트

- 멤버 프로필

 

브레이브걸스에 의한 브레이브걸스를 위한 브레이브걸스 어플리케이션을 만들어보자!

 

그리고 생각난게 바로 플러터였다.

플러터로 동시에 Android와 iOS를 출시하자!

 

바로 와이어프레임을 만들기 시작했다.

일주일 정도가 흘렀을까?

와이어프레임과 기능 정의는 잡혔는데...

욕심이 났다.

 

한달 뒤면 새로운 앨범으로 컴백을 한다는데, 그 전에 만들고 싶었기 때문이다.

 

그렇게 무작정 커뮤니티에 글을 올린다.

어플의 취지와 와이어 프레임을 올렸고 개발자 외에도 "덕질 전문가"도 모집했다.

팬 활동을 처음 하는 나는 생소한게 많았기 때문이다.

 

그렇게 오픈채팅방엔 10명 남짓 모이게 된다...

모두 현업 10년차 이상 아재들이였다.

그렇게 판이 커지게 되었다.

 

포지션 분배 부터하고 4개의 팀으로 나뉘어 졌다.

- 기획/디자인

- 클라이언트

- 서버

- 마케팅/덕질 자문

 

일주일에 2번씩 2~3시간 가량 회의를 진행했다.

나머지 시간에도 오픈채팅방은 뜨겁게 달아올랐다.

 

서버인프라는 AWS를 이용해 LEMP으로 플랫하게 구성하고 서버팀에게 인계했다.

 

서버팀은 DB 모델링을 빠르게 시작했고, 필요한 API들을 만들어 갔다.

나를 포함한 프론트팀은 디자인이 넘어올 때 까지 프레임을 만들고 더미 데이터로 초기 작업을 시작했다.

 

프론트팀이 2명으로 시작했지만,

그 중 한명은 현생의 바쁨으로 조용히 잊혀져 갔다. 또르르... 😭

 

나는 백수이기 때문에 온전히 하루를 쓸 수 있었다.

충분히 커버가 가능했기 때문에 프로젝트에는 지장이 없었다.

 

고마웠던건 현업에서 잔뼈가 굵은 선배들이 현업 경험을 토대로

우리 프로젝트에 쓰면 좋을 SaaS들을 추천해줬고

그 덕분에 수월하게 일처리를 할 수 있었다.

 

디자이너와 함께 일하는 것도 너무 재밌었다.

평소 디자인에도 관심이 많았던 나는 함께 일하게 되면서 AdobeXD를 익히게 되었다.

디자이너님은 중견 기업 과장님 이신데 소통이 잘되는 개발자라며 칭찬해 주셨다😅

 

그렇게

첫 커밋을 한지 딱 28일이 지나고 드디어 스토어에 출시 승인이 완료되었다.

iOS도 빠르게 승인되어 3일 후 두개의 플랫폼에 모두 출시 되었다.

 

6월 15일 브레이브걸스의 컴백 D-2 드디어 피어레스 플러스(Fearless +)가 탄생되었다.

 

반응은 뜨거웠다.

하루만에 800명이 다운받았고 3일이 지나자 다운로드 수는 1800명이 되었다.

 

지금은 나머지 맴버들에게 앱을 이관했고,

이후 유지보수를 위해 네이트브로 개발 되어 운영 중이다

 

처음 접해본 플러터와 dart로 구글신에게 애원하며 만든 첫 앱이 너무나 자랑스럽다.

 

그 동안 나름 잡부 마인드로 이거 저거 배워왔던 것 덕분에 서비스 전반적인 리딩을 할 수 있었고

운좋게도 현업 선배들과 함께 작업 하게 되어 배우고 느낀 것들이 참 많은 프로젝트 였다.

 

앞으로 프로젝트를 진행할 때 엄청난 도움이 될 것 같다.

 

 

 

피어레스 플러스 만나러 가기

 

 

 

 

728x90

플러터와 안드로이드간 API통신을 하기 위한 채널을 구현해 봤다

 

플러터와 안드로이드 플랫폼를 연결하는 채널 구현 요약

 

1. 플러터에서 MethodChannel 객체를 고유한 채널명으로 만들어 준다

1-1. 안드로이드에서 만들어둔 메서드를 invoke(호출)한다

static const platform = const MethodChannel('atanasio.dev/method'); //채널명은 고유해야 한다

Future<void> getBatteryLevel() async {
    try {
      final int result = await platform.invokeMethod('getBatteryLevel'); // 안드로이드에서 작성한 메서드 invoke
      _batteryInfo = '$result'; // int to String

    } on PlatformException catch (e) {
      print('error : ${e.message}');
    }
  }

  Future<void> getAndroidVersionName() async {
    try {
      final String result = await platform.invokeMethod('getAndroidVersion');
      _androidVersion = '$result';

    } on PlatformException catch (e) {
      print('error : ${e.message}');
    }
  }

  Future<void> getAndroidModelName() async {
    try {
      final String result = await platform.invokeMethod('getAndroidModelName');
      _androidModelName = '$result';

    } on PlatformException catch (e) {
      print('error : ${e.message}');
    }
  }

 

2. 안드로이드에서 다음 코드를 준비한다

package ...

import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.*

class MainActivity : FlutterActivity() {
  private val CHANNEL = "atanasio.dev/method" // 플러터에서 만든 채널명과 일치해야 한다.

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
      super.configureFlutterEngine(flutterEngine)
      MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
      // 플러터 호출 메서드 분기처리
        when (call.method) {
          "getBatteryLevel" -> { // 플러터에서 호출한 메서드 * invokeMethod('getBatteryLevel');
            val batteryLevel = getBatteryLevel() // 함수 호출
            if (batteryLevel != -1) result.success(batteryLevel) // 성공시 플러터로 콜백
            else result.error("UNAVAILABLE", "Battery level not available.", null)
          }
          "getAndroidVersion" -> {
            val androidVersionCode = getAndroidVersion()
            if (androidVersionCode != null) result.success(androidVersionCode)
            else result.error("UNAVAILABLE", "Android Version not available.", null)
          }
          "getAndroidModelName" -> {
            val modelName = getAndroidModelName()
            if (modelName != null) result.success(modelName)
            else result.error("UNAVAILABLE", "Android deviceName not available.", null)
          }
          else -> result.notImplemented()
        }
      }
    }
    
    // 배터리 값 구하기 (공식문서 예제)
    private fun getBatteryLevel(): Int {
      val batteryLevel: Int
      if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
        val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
        batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
      } else {
        val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
        batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
      }
      return batteryLevel
    }
    
	// 안드로이드 버전( 엔드유저용 )
    private fun getAndroidVersion(): String {
      return VERSION.RELEASE
    }

	// 모델명
    private fun getAndroidModelName(): String {
      return MODEL
    }
  
}

 

플러터의 UI 코드 부분은 생략 하였습니다

필요하신분은 댓글로 요청해 주세요

 

 

 

나는 하나의 채널명으로 여러개의 메서드를 만들어 사용했다.

기기 기본정보에 관한 메서드였는데, 

네이밍을 "atanasio.dev/androidDeviceInfo" 로 했으면 좋았을 것 같다.

 

값들이 null로 나와 당황했었는데

플러터에서만 빌드를 해서 그랬던것 같다

안드로이드에서 빌드를 하니 해결되었다

 

 


 

 

pub.dev에 퍼블링싱 되어 있는 패키지들이

대부분 채널을 이용해서 만들어 진걸 알수 있었다.

 

안드로이드의 대표적인 내부저장소 SPF

 

 

느낀점

- pub.dev에 이미 많은 패키지들이 존재한다는걸 알게되었다.

  이것들이 채널링을 통해 만들어졌다는걸 알게되었다.

 

- ndk를 이용하는것도 재밌을것 같다.

 

- 다음엔 플러터에서 카메라 필터효과 주는걸 구현해 봐야겠다.

728x90

✋ WARNING

글을 시작하기 전, 당부 말씀드립니다.

이 포스팅은 잘못된 개념을 전달할 수 있으며, 개인적으로 공부한 내용을 적어놨습니다.

댓글로 틀린 내용이 있으면 지적질 피드백해주세요!!

 

 

안녕하세요 오늘도 좋은 하루입니다!

플러터 기초 개념 정리 중인데 피드백 부탁 글? 남겨 보아요 ㅎㅎ
stateless와 stateful가 따로 존재하는 개념을 정리하고 있습니다.

(stateful만 있으면 되지 왜 두 개를 만들어 뒀을까 하는 의문에서 시작)

1. 플러터는 모든 위젯을 직접 그린다. ( 스키아 엔진 )
2. Stateful 보다 Stateless이 드는 비용이 적다. 랜더링 속도가 빠르고. 한 번만 그린다. ( 라이프사이클이 단순함 )
3. 비용이란, 플러터가 위젯을 그리는 공수이다. 
4. 사용자 이벤트가 없는 위젯까지 Sateful로 만들면 굳이 멀쩡히 그려 논 걸 버리고 다시 그려야 한다.
5. Stateless 위젯으로 부모 위젯을 만들고 Stateful이 필요한 컴포넌트들만 따로 만들어 사용한다.
6. 이런 이유 등을 반영해서 비즈니스 로직과 컴포넌트를 분리한 Bloc 패턴이 등장했다.

피드백 진짜 감사히 받겠습니다!
예)
-> 6번은 OOO 보다 OOO이라고 보는 게 맞아요 더공부하세요!
-> 2번 내용은 OOO 해서 잘 정리된 것 같아요

728x90

서론없이 본론으로 바로 가겠습니다.

BOTTEM OVERFLOWED BY 29PIXELS

텍스트필드를 터치했을때 키보드가 올라오면서 밀려 29픽셀이 오버플로우 되었습니다.

 

제가 해결한 방법은 Scaffold의  resizeToAvoidBottomInset: false, 속성을 주었습니다.

 

@override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Colors.white, 
        resizeToAvoidBottomInset: false, // resizeToAvoidBottomPadding 은 deprecated 되었습니다
        body: SafeArea(
        child: ....
        )
     );
  }

 

 

빌드 결과

해결 되었습니다.

728x90

안드로이드 스튜디오를 실행하려던 찰나. 실행이 안되네요... 호들짝 놀랬습니다.

 

대충 익셉션 터진짤

진정하고 에러를 긁어서 구글신에게 여쭤보았죠

이게 언제나 젤 빠르더라고요 

 

해결법은

cmd 실행
netsh int ipv4 set dynamicport tcp start=49152 num=16383
netsh int ipv4 set dynamicport udp start=49152 num=16383

net stop winnat
net start winnat

이렇게 포트의 사용범위를 늘리고

winnat 서비스를 재시작 해서 해결할 수 있었습니다.

(본문에는 winnat 서비스 재시작으로 대부분 해결된다네요)

참고 : https://youtrack.jetbrains.com/issue/IDEA-238995

 

 

 

728x90

아직도 헤깔리는 Context

 

이렇게 예시를 들어서 정리하면 머리속에 잘 정리될것 같아 블로깅한다.

 

말그대로 Context의 뜻은 맥락이다.

 

Application에서의 맥락이란 무엇일까?

 - Application을 하나의 소설책으로 비유해 보자.

 

예시로 목차를 보면 아래와 같다.

 

Application의 구성도 책과 비슷하다.

책의 각 챕터와 같이 주제별로 페이지들이 존재한다.

그 페이지 안에는 다양한 Component들이 존재한다.

( Button, TextView, ImageView, CheckBox 등)

책에서는 이 컴포넌트를 하나의 단어, 또는 문장이라고 비유하자.

 

문장들이 모여 하나의 문단을 만들고 그 문단들이 모여 스토리가 이어지게 된다.

여기서 맥락이란 지금 책을 읽고 있는 부분의 스토리일 것이다.

 

다시 말해 내가 지금 보고 있는 맥락은 해당 챕터가 다루고 있는 내용을 보면 알수있다.

 

정리해보자.

 

Application에서 Context가 담고 있는 내용은 책의 챕터별 주제와 같다.

 

안드로이드의 Toast 메시지를 띄우는 메서드에 Context를 필요로한다.

 

MainActivity에서 Toast메서드를 호출할때는 Context인자로 this를 넣으면 된다.

 

this는 MainActivity를 가르킨다. 즉, "Toast메시지를 띄웁니다." 라는 문장의 앞뒤 맥락은 MainActivity라는 것이다.

다시말해 MainActivity를 다루고 있다라는걸 전달해서 "Toast메시지를 띄웁니다."라는 메서드의 맥락을 만들어준 것이다.

 

같은 문장이라도(같은 메서드라도) 전후 맥락이 다르다면 엉뚱한 말이 될수 있지 않겠는가?

그래서 꼭 맥락( Context )을 지정해 주어야 하는것이다.

 

 

이번 블로깅에선 맥락이라는 말뜻 자체에 대해서 나름대로 정리해 봤다.

다음에는 Android Context가 구체적으로 어떻게 구성되어 있는지에 대해 디테일하게 블로깅해야겠다.

 

다음 블로깅에 관한 레퍼런스

shnoble.tistory.com/57

728x90

신경망 첫걸음 표지이미지

 

 

다양한 분야에서 개발 엔지니어로 일하고 있는분들이 모인 단체 채팅방에서 북세미나를 주기적으로 하고있다.

이번에 신경망 첫걸음이란 흥미로운 책으로 북세미나가 있어 슬그머니 참석했다.

 

발표자님의 간결하면서도 핵심적인 요약 덕분에 값진 지식을 얻게되었다.

여운이 남아 나도 책을 구매해 보았다.

 

오늘부터 조금씩 읽어가며 조금씩 블로그에 정리해볼 예정이다.

 

나를 위해, 그리고 이 글을 읽어주시는 분들을 위해

 

 

해당 포스팅 바로 가기

+ Recent posts