티스토리 뷰

Flutter로 처음 앱을 만들면  특별한 설정 없이도 flutter화면이 정상적으로 잘 나오는 것을 확인할 수 있다. 

그리고 보통은 그 첫번째 화면에서부터 앱을 만들어 가기 시작한다. 

사실 android에서 flutter화면을 띄우기 위해서 어떤 일을 하는지 알지 못해도 우리가 앱을 개발하고 실행하는데 아무런 문제가 없다.

하지만 Flutter가 구동되는 원리를 조금만 더 이해하고 개발한다면 Android Native와  Flutter을 유연하게 구성해서 다양한 요구사항을 처리할 수 있다. 

FlutterEngine의 생성 

FlutterEngine 생성하는 기본 코드를 확인하면 OS에서 Dart 코드를 어떻게 실행하게 되는지 알수 있다.

이를 알면 FlutterEngine 좀더 다양하게 설정해서 사용하는 것이 가능하다.

val flutterEngine = FlutterEngine(this)
flutterEngine
    .dartExecutor
    .executeDartEntrypoint(
        DartExecutor.DartEntrypoint.createDefault()
    )

위 코드는 android에서 FlutterEngine을 생성하는 코드이다.

일반 클래스처럼 flutterEngine 객체를 만들고 몇 가지 설정만 해주면 그대로 사용할 수 있다. 

생각보다 특별한 것 없이  쉽게 생성할 수 있다. 

위 코드에서 executeDartEntrypoint를 DartExecutor.DartEntrypoint.createDefault()를 사용하고 있는 것을 눈여겨보자 

이 부분의 구현은 아래와 같이 되어 있다. 

@NonNull
public static DartEntrypoint createDefault() {
  FlutterLoader flutterLoader = FlutterInjector.instance().flutterLoader();
  if (!flutterLoader.initialized()) {
    throw new AssertionError(
        "DartEntrypoints can only be created once a FlutterEngine is created.");
  }
  return new DartEntrypoint(flutterLoader.findAppBundlePath(), "main");
}

위 코드에서 DartEntirypoint를 생성하기 위해 bundlePath와 "main"을 인자로 넣어주고 있는 것을 확인할 수 있는데 이는 기본 설정인 /lib/main.dart 파일과 main() 함수를 기본 실행 포인트로 설정하는 것을 의미한다.

만약 위에서 "main"대신 "main02"로 함수명을 변경하면 main.dart 파일에서 main02 함수를 실행할 것이다.

 

즉 flutterEngine.dartExecutor.executeDartEntrypoint의 기본 설정이  /lib/main.dart 파일의 main()이기 때문에 우리가 만든 프로젝트의 main.dart 파일 속  main() 함수가 실행되면서 flutter가 시작되는 것을 알 수 있다.

 

만약 executeDartEntrypoint를 다른 것으로 변경하면 어떻게 될까? 

하나의 앱에서 main01()과 main02()로 구분해서 Flutter화면을 따로 실행하도록 할 수 있을까?

물론 가능하다. 실제로 시작 포인트를 다르게 주고 여러 FlutterActivity를 이용해서 앱을 구성할 수도 있다. 

 

FlutterEngine이 생성 후 entryPoint가 설정되고 나면 그 즉시 main.dart의 main() 함수가 호출된다.  하지만 이렇게 dart코드가 호출되더라도 화면에 바로 표시되지는 않는다.  FlutterEngine은 어디까지나 Dart와 Flutter코드를 구동하고 해석해서 Native에서 활용할 수 있도록 해주는 엔진이기 때문이다. 

FlutterView 생성

Flutter가 화면에 표시되기 위해서는 FlutterView을 생성한 후 FlutterEngine과 연결해주는 과정이 필요하다. 

FlutterView의 생성은 FlutterActivityFragmentDelegate.onCreateView() 함수에서 쉽게 확인할 수 있다.   

View onCreateView(
      LayoutInflater inflater,
      @Nullable ViewGroup container,
      @Nullable Bundle savedInstanceState,
      int flutterViewId,
      boolean shouldDelayFirstAndroidViewDraw) {

    if (host.getRenderMode() == RenderMode.surface) {
      FlutterSurfaceView flutterSurfaceView =
          new FlutterSurfaceView(
              host.getContext(), host.getTransparencyMode() == TransparencyMode.transparent);
      host.onFlutterSurfaceViewCreated(flutterSurfaceView);
      flutterView = new FlutterView(host.getContext(), flutterSurfaceView);
    } else {
      FlutterTextureView flutterTextureView = new FlutterTextureView(host.getContext());
      flutterTextureView.setOpaque(host.getTransparencyMode() == TransparencyMode.opaque);
      host.onFlutterTextureViewCreated(flutterTextureView);
      flutterView = new FlutterView(host.getContext(), flutterTextureView);
    }

    flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
    flutterView.attachToFlutterEngine(flutterEngine);
    flutterView.setId(flutterViewId);
	
    return flutterView;
}

위 코드를 보면 FlutterView가 surfece 모드 texture모드 중 하나로 생성되는 것을 확인 할 수 있는데 기본 설정은 surface모드이며 이때는 FlutterView위에 다른 View를 표시할 수 없으며 texture모드 일 때는  FlutterView위에 다른 View를 표시할 수 있다.

 

생성된 FlutterEngine와 FlutterView는 아래와 같이 view를 engine에 attach 해주는 것으로 연결시킬 수 있다.

flutterView.attachToFlutterEngine(flutterEngine);

이제 FlutterView와 FlutterEngine이 연결되었고, 화면에서 우리가 알고 있는 FlutterUI들을 볼 수 있게 되었다. 

 

FlutterActivity로 FlutterEngine과 View 표시하기

FlutterEngine과 FlutterView를 직접 생성하고 서로 연결하는 것은 많이 번거로운 일이다.  기본적인 생성외에도 여러 가지 listener들을 설정해야 하는 번거로움도 있다.  그래서 실무에서는 FlutterActivity나 FlutterFragment를 활용해서 Engine과 View를 생성한다. 

// 새로운 FlutterEngine을 생성해서 사용하는 FlutterActivity를 실행한다. 
// FlutterEngine의 기본설정인 main.dart의 main함수를 실행할것이다.
val intent = FlutterActivity
            .withNewEngine()
            .build(this)
startActivity(intent)

// 미리 생성해둔 engine02를 사용하는 FlutterActivity를 실행한다.
val intent = FlutterActivity
  .withCachedEngine("engine02")
  .build(this)
startActivity(intent)

 

FlutterActivity나 FlutterFragment를 사용해서 engine을 띄울 때는 새로운 engine을 생성해서 사용하거나 이미 생성된 engine을 이용할 수 있다. 

만약 새로운 engine을 생성할 경우 FlutterActivity가 생성되는 시점에 engine이 만들어지는데, engine이 생성될 때 1초 정도의 시간이 소요되기 때문에 잠깐 화면이 멈춘 것 같은 부자연스러운 느낌을 받게 된다. 

이를 해결하기 위해서는 미리 engine을 생성해두고 이것을 활용해서 Activity을 시작하는 방법이 있다. 

 

FlutterEngineCache를 활용한 Engine사용 

FlutterActivity를 이용해 새로운 화면을 띄울 때 미리 생성해둔 engine을 이용하면 훨씬 빠르고 자연스럽게 화면이 이동한다.  보통 앱이 처음 시작할 때 Application에서 Engine을 미리 만들어 두고 그것을 재사용한다.  한번 만들어진 Engine는 그 참조값이 유지되는 한 메모리에서 해제되지 않고 다시 불러와 사용하더라도 이전에 사용하던 모습(Flutter내부 화면)을 그대로 표시할 수 있다. 

// 기본설정을 따르는 enigne01
val engine01 = FlutterEngine(this)
engine01
    .dartExecutor
    .executeDartEntrypoint(
        DartExecutor.DartEntrypoint.createDefault()
    )

// main.dart의 main02() 실행하는 engine02
val flutterLoader = FlutterInjector.instance().flutterLoader()
val engine02 = FlutterEngine(this)
engine02
  .dartExecutor
  .executeDartEntrypoint(
    DartEntrypoint(flutterLoader.findAppBundlePath(), "main02")
}

// engine01과 engine02를 engineCache에 등록한다.
FlutterEngineCache.getInstance().put("engine01", engine01)
FlutterEngineCache.getInstance().put("engine02", engine02)

 이제 enigne01과 engine02가 만들어졌고 우리는 필요에 따라 각 engine을 가져다 사용할 수 있다. 

 

마치며 

앱의 기능이 많아지고 여러 가지 요구사항들을 적용하다 보면 Flutter화면 하나만으로 해결이 되지 않을 때도 있습니다.

Android용 라이브러리나 Activity를 직접 구현해서 사용해야 하는 경우도 있으며, 여러  Flutter화면과 Android Activity / Fragment를 복합적으로 활용해야 할때도 있습니다.

하나의 앱에서 여러 Flutter화면과 Engine을 사용 하는 것이 가능하다는 것을 알고 있다면 우리가 만들 제품들을 좀 더 유연하고 다양한 방법으로  만들 수 있을 것입니다.

 

참고자료: 

https://docs.flutter.dev/development/add-to-app/android/add-flutter-screen?tab=prewarm-engine-kotlin-tab 

 

Adding a Flutter screen to an Android app

Learn how to add a single Flutter screen to your existing Android app.

docs.flutter.dev

 

https://developpaper.com/a-zero-intrusion-and-efficient-flutter-hybrid-stack-management-scheme-is-worth-having/

 

A zero intrusion and efficient flutter hybrid stack management scheme is worth having - Develop Paper

In the actual working scenario, it is difficult for us to build a project with pure fluent from scratch. It is precisely because of this that we have to consider the jump management of native + fluent hybrid stack first in hybrid development, because it is

developpaper.com

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함