단위 테스트를 작성하기 위해서, 특히 Mock 혹은 Fake 를 사용하기 위해 초기화 메소드에 의존성을 전달받는 경우가 있었습니다. 그럴 때, 해당 값을 하위에 클래스(내부에 초기화된 클래스) 에서 사용하게 되면, 이것을 초기화 함수나 멤버변수로 계속 전달해줘야 합니다. 이 부분을 프로그래밍하다가, 이것을 부르는 용어나 어떤식으로 해결했는지 궁금해서 이 글을 작성합니다.
특정 객체가 있고, 그것의 하위 객체가 있습니다. 하위 객체에게 필요한 값을 전달해주는 행위를 프로퍼티 드릴링이라고 부릅니다.
struct ContentView: View {
var welcomeMessage = "Hello SwiftUI~!!"
var body: some View {
VStack {
Text(welcomeMessage)
}
.padding()
}
}
welcomMessage
라는 프로퍼티를 입력받아서 전달받고 있습니다.struct ContentView: View {
var welcomeMessage = "Hello SwiftUI~!!"
var body: some View {
VStack {
BodyView(message: welcomeMessage)
}
.padding()
}
}
struct BodyView: View {
var message: String
var body: some View {
TextView(text: message)
}
}
struct TextView: View {
var text: String
var body: some View {
VStack {
Text(text)
}
}
}
ContentView > BodyView > TextView
로 구성된 View 입니다.위 상황에서 하위뷰가 더 복잡해지면, 전달해줘야하는 프로퍼티가 정말 많아질 것 같습니다.
최초에는 텍스트가 최상위 위젯에 위치합니다.
void main() {
runpApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Wdiget build(BuildContext context) {
return Scaffold(
body: Text('이곳은 최상위 위젯임')
);
}
}
그런데 하위에 SubWidget 들이 추가된다면, 다음과 같이 프로퍼티를 전달해줘야 하는 상황은 동일합니다.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
body: Text(''),
);
}
}
class SecondWidget extends StatelessWidget {
SecondWidget({
Key? key,
required String this.message,
}) : super(key: key);
String message;
Widget build(BuildContext context) {
return Text(message);
}
}
프로퍼티를 전달하게 되면, 초기화 메소드만 봐도 어떤 값들에 의존하고 있는지 확실히 알 수 있습니다.
단점으로는 프로퍼티 하나의 코드 변경 시, 하위 뷰를 모두 변경해야 한다는 단점이 있습니다.
이를 예방하는 방법으로 ServiceLocator(서비스 로케이터)
패턴을 사용하기도 합니다.
(SwiftUI / Flutter)
// ObservableObject 만들기
class Message: ObservableObject {
var content: String = "Hell wolrd SwiftUI~!"
}
@main
struct Property_DrillingApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(Message())
}
}
}
struct ContentView: View {
var body: some View {
VStack {
BodyView()
}
.padding()
}
}
struct BodyView: View {
var body: some View {
TextView()
}
}
struct TextView: View {
@EnvironmentObject var globalMessage: Message
var body: some View {
VStack {
Text(globalMessage.content)
}
}
}
@EnvironmentObject
를 사용해서, 필요한 곳에서만 정의해서 접근하고 있습니다.class MyApp extends InheritedWidget {
MyApp({
super.key,
required super.child,
});
String message = 'Hell world Flutter~!';
static MyApp of(BuildContext context) {
final MyApp? result = context.dependOnInheritedWidgetOfExactType<MyApp>();
assert(result != null, "Not found...");
return result!;
}
Widget build(BuildContext context) {
return Scaffold(
body: SecondWidget(),
);
}
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
throw UnimplementedError();
}
}
static MyApp of(BuildContext context) {...}
이 부분을 전역으로 정의했기 때문에, 하위 위젯에서도 상위 위젯에 접근가능합니다.
다만 Flutter 프레임워크 특징 중 현재 Context 를 입력받아서, 상위 위젯을 조회해야합니다. 그러므로 BuildContext 를 파라미터로 전달해줍니다.
최하위 위젯
class ThridWidget extends StatelessWidget {
ThridWidget({
Key? key,
}) : super(key: key);
Widget build(BuildContext context) {
return Text(MyApp.of(context).message);
}
}
MyApp.of(context).message
이 코드가 상위 위젯으로 접근하는 부분입니다.