OpenFramework를 Windows Universal App에서 사용해보기

최근에 OpenFramework(이하 OF)가 Windows Universal App에서도 사용할 수 있게 되었다. 직접 한번 간단히 해본 내용을 공유한다.

아직 나온지가 얼마 안 되서 그런지 친절한 튜토리얼은 없었다. MS오픈테크의 공지를 읽어 보니까 projectGenerator라고 하는 OF의 프로젝트 생성 툴이 있는데 이를 Universal App 프로젝트를 만드는 버전으로 포팅했다는 내용이 있다.

따라서, 해당 공지에 있는 GitHub의 OF 프로젝트를 Clone해봤다.

image

GitHub for Windows를 설치한 상태에서 Clone in Desktop을 클릭하여 프로젝트를 로컬로 Clone한다.

image

openFrameworks 프로젝트를 선택하고 Branch를 universal로 변경한다.

 

image 

Clone 된 폴더를 열고, projectGenerator라는 하위 폴더에 들어가면 projectGeneratorSimple_winrt.exe라는 실행파일을 찾을 수 있다.

이 파일을 실행하면 다음과 같은 GUI 툴이 나온다.

 

image

Name을 적당히 변경하고 GENERATE PROJECT 버튼을 눌러서 프로젝트를 생성한다.

image 

솔루션 파일을 열면 다음과 같은 구조로 되어 있다.

image

이 중에 ofApp.cpp가 openFramework를 사용하는 코드가 들어가는 파일이다. 현재는 아무런 코드가 작성되어 있지 않기 때문에 실행하면 회색 화면만 보인다.

image

이제부터는 openFramework.org의 튜토리얼을 따라서 ofApp.cpp나 opApp.h를 수정하면 된다.

image

ofApp.cpp 파일은 위와 같이 미리 정해진 메서드가 있고, 해당 메서드의 내용을 작성하면 되는 구조이다. setup()은 처음에 한번만 실행되고, draw()는 프레임 단위로 화면을 갱신하고, update()는 반복 실행하는 코드를 넣으면 된다.

여러 가지 튜토리얼 중 파티클 시스템을 따라해 봤다. 마우스의 클릭에 따라 원이 따라 움직이는 예제이다.

image

간단히 코드를 살펴보면 위와 같이 of로 시작하는 기본 제공 함수들을 이용하여 마우스 위치(mouseX, mouseY)를 가져와서, 색상을 정하고(ofBackgroundGradient(…), ofSetColor(…), ofFill() 등), 원을 그려 넣는 등(ofCircle(…)) 쉽게 구현을 해볼 수 있게 되어 있다. Shared 프로젝트에 ofApp.cpp 파일이 있어서 윈도우/윈도우폰 프로젝트 설정만 바꾸면 동일하게 실행되는 것을 확인할 수 있다.

_968

_969

간단히 openFramework가 유니버설 앱에서 동작하는 것을 확인하였는데, OpenGL이나 OpenCV 등도 동작하는지는 좀 더 살펴보아야 할 것 같다.

Advertisements

.NET Micro Framework 살펴보기

.NET Micro Framework는 .NET Framework의 가장 작은 버전으로 아주 작은 크기의 디바이스에서 제한된 파워와 메모리 상에서 구동하는 것을 목표로 만들어졌다. 00~000 MHz 정도의 속도와 KB 단위의 메모리에서 동작한다. 원래는 SPOT 시계용으로 디자인이 되었으며, 다양한 디바이스로 포팅되었다. 오픈소스 프로젝트로 모든 소스가 공개되어 있으며 공식 사이트가 있다. 40% 코드가 커뮤니티에서 작성한 코드라고 한다.

이 글에서는 공식 사이트의 소개 동영상을 통해 .NET MF를 살펴보고자 한다.

개발도구

image

영상이 2012년에 촬영된 것이라 최신 정보는 아니지만 필요한 기본 도구는 위와 같다. 현재 .NET MF 버전 4.3이 출시되어 있으며 Visual Studio 2012를 지원한다. 자세한 내용은 여기서 참고할 수 있다.

최근의 블로그 포스팅을 보면 VS 2013 버전용으로 통합작업을 진행 중이며 향후 몇 주 안에 출시될 예정이라고 한다. Express 버전의 무료 툴이 있으며, Mac이나 Linux에서는 Windows VM과 VS을 쓰거나, Monodevelop을 이용해서 개발할 수 있다.

 

디바이스 형태

디바이스 형태에는 다음과 같은 4가지로 분류할 수 있다.

  1. Arduino 스타일
    image
    • Netduino, Netduino Plus
    • FEZ Panda 류
  2. 소켓 스타일
    image
    image
    image
    • .NET Gadgeteer
    • Netduino GO!
  3. 개발용 보드
  4. 소형 형태
    • Netduino Mini
    • FEZ Mini, Cerb40
    • GHI G120

image

아두이노 호환보드

image

표준화된 소켓을 이용한 히드라 보드

image

image

다양한 소켓 방식의 모듈과 전원 공급을 제어할 수 있는 4개의 릴레이가 적용된 보드(맨 우측)

image

넷두이노 미니가 들어간 게임 디바이스

아키텍쳐

image

아두이노와 유사하게 개발용 머신과 타겟 디바이스가 있으며, 개발-컴파일-배포-디버깅은 개발용 머신에서 작업을 한다. 타겟 디바이스에서는 부트로더 펌웨어가 NETMF Runtime을 로드하고 중간 언어로 컴파일된 코드를 실행한다.

용도

image

메이커들이 .NET MF로 만드는 프로젝트들의 리스트이다. 시계에서부터 수족관 제어, 로봇, IoT 디바이스 등 다양한 것을 만들 수 있다. 홈 모니터링/보완도 큰 분야 중에 하나이다. TCP/IP가 구현된 초소형 웹서버를 구동할 수 있다.

.NET MF를 이용하는 가장 큰 목적은 하드웨어 아이디어를 빠르게 프로토타이핑하는 것이다. 또한, 개발 스킬을 연습할 수 있는데, 저용량 메모리와 저전력의 CPU파워, 적은 분량의 코드로 작성을 해야 하기 때문이다. C# 개발자들에게는 C나 어셈블리를 배우지 않고도 디바이스 개발을 할 수 있다는 것이 장점이다.

사례

image

게임기

image

윈도우폰으로 잔디 스프링클러를 동작하는 사례

image

음악 악기 사례

image

플로피 디스크로 스타워즈 테마곡 연주

image

센서로 전방을 인식해서 이동하는 로봇

개발

image

VS에서 새 프로젝트 메뉴를 실행하여 Visual C# > Micro Framework를 선택하면 위와 같이 Netduino 어플리케이션 프로젝트를 생성할 수 있다.

image

Program.cs 파일을 열면 위와 같은 코드를 볼 수 있다. Reference 추가를 통해서 버튼, LED, 가변저항을 처리하는 라이브러리를 불러와서 사용할 수 있다.

image

일반 프로젝트와 다른 점은 Main() 함수의 마지막에 Thread.Sleep(Timeout.Infinite); 를 추가해서 프로그램이 계속 실행되도록 하는 점이다.

완성된 코드를 Build하고 Start Debugging을 실행하면 된다.

image

하단의 버튼을 눌렀을 때 좌측 상단의 LED가 들어오는 것을 확인할 수 있다.

image

Breakpoint를 이용해서 Debug를 할 수 있다.

image

위 사진은 보드에 연결한 MIDI 모듈과 MIDI 인터페이스 신디사이저를 연결한 데모이다.

image

Gadgeteer 모듈을 드래그앤드롭으로 디자인할 수 있는 화면이다. 좌측 상단에 MIDI 인터페이스 모듈을 볼 수 있다.

image

우측 상단의 터치 패널을 통해서 일종의 음악을 만드는 디바이스 데모이다.

image

이 데모는 손 아래 쪽에 있는 초음파 거리 센서를 이용해서 측정한 손과 보드 사이의 거리를 MIDI 음으로 변환하여 소리를 연주하는 데모이다.

image

image

ParallaxPingModule로 거리를 측정하는 코드이다.

image

_ping.GetDistance(…)를 이용하여 거리 값을 측정한 후에 거리에 따라 음을 만들고, midi.SendNoteOn(…)으로 소리를 보낸다.

image

.NET Gadgeteer 어플리케이션을 만드는 화면이다. Micro framework 대신 Gadgeteer를 선택하였다.

image

Program.gadgeteer 파일을 열고 위와 같이 드래그앤드롭으로 보드와 모듈들을 배치하고 소켓을 연결시킨다. 각 모듈 밑에 button, led라는 인스턴스 이름을 지정할 수 있다. 위와 같은 방식으로 실제 보드와 모듈도 연결시킨다.

image

Program.generated.cs 파일을 열어보면 위와 같이 초기화 코드가 생성되어 있다. 이 프로젝트의 경우 Thread.Sleep(Timeout.Infinity)가 필요 없다.

image

위와 같이 button, led 인스턴스를 이용하여 동작 코드만 작성하면 실행시킬 수 있다.

image

버튼을 눌러서 초록색 LED를 점등하는 화면이다. 소켓 방식을 이용한 Gadgeteer를 이용하면 보다 쉽게 하드웨어 프로젝트를 만들어 볼 수 있다.

여기까지 간단히 .NET Micro Framework를 살펴보았다. 최근 메이커 커뮤니티가 커지면서 많은 사람들이 직접 하드웨어를 만드는 취미 생활을 가지고 있다. 또한, 미국에서는 이러한 취미 활동이 보다 전문화 되면서 대기업들도 관심을 가지고 관련 산업이 점차 커지고 있는 추세라고 한다. .NET Micro Framework도 최근 1~2년간 별다른 업데이트가 없다가, 최근에 IoT의 유행과 함께 다시 주목을 받으며 마이크로소프트에서도 IoT 전담 팀이 꾸려지고 Visual Studio 2013 버전용으로 새로 개발이 시작되었다. 앞으로 어떻게 변화하고 응용될지 관심을 가지고 지켜보면 재미있을 것 같다.

image

참고로 IoT는 다양한 디바이스 크기에 따라 개발 플랫폼이 다르게 구성될 수 있다. .NET Micro Framework는 가장 작은 Micro 수준의 디바이스용으로 만들어진 플랫폼이다. IoT 디바이스 분류 및 기술에 대해서는 여기를 참고하자.

하이브리드 앱 개발 on Visual Studio #4

WinJS 앱을 살펴보도록 하자.

index.html

index.html에는 WinJS 컨트롤을 사용하고 있다.

image

data-win-control과 data-win-bind는 WinJS에서 컨트롤을 사용할 때 사용하는 확장 속성들이다. data-win-control은 컨트롤로 동작하게 할 때 사용하며, data-win-bind는 바인딩 기능을 이용해서 특정 변수 값과 요소의 속성을 연결시켜 준다.

처음에 div 태그에 WinJS.Binding.Template이 data-win-control에 지정되었는데, 이 부분이 추후에 템플릿으로 사용됨을 알 수 있다. 또한, input 태그에는 data-win-bind에 value: text가 적용되어서, 해당 컨텍스트(바인딩에서 사용하는 데이터 오브젝트)의 text 값을 value 속성에 바인딩 시키고 있다.

image

이 부분은 실제 앱의 UI 부분이다. 할일 내용을 넣는 부분에는 onchange 이벤트에 Xplat.datasource.addToDo(event.target) 함수가 핸들러로 지정되어 있다.

또한, main 섹션의 div 요소는 WinJS.UI.ListView로 선언되었다. data-win-options 속성을 통해 ListView 생성에 필요한 설정들을 전달한다. 여기에는 itemDataSource(리스트 데이터), itemTemplate(앞서 정의한 각 항목을 담는 템플릿 코드), layout(GridLayout과 ListLayout 중에 선택) 등이 포함되어야 한다.

index.ts

그럼 계속 index.ts 파일을 살펴보자. TypeScript를 사용하고 있기 때문에 JS에서 몇 가지 확장 키워드들이 사용되고 있다.

image

module은 코드를 모듈화 하는데 사용되는 키워드이며, 재사용성을 높여주고 네임스페이스를 만든다. export 명령을 통해 해당 네임스페이스에서 사용 가능한 클래스와 변수 등을 정의할 수 있다.

여기서는 Xplat.app 변수를 XplatApplication 형으로 선언하고, Xplat.XplatApplication 클래스를 정의하고 있다. TypeScript는 JS와 달리 클래스 정의를 위해 class 키워드와 멤버 관련 문법을 제공한다.

image

계속해서 Xplat.ApplicationEvents를 정의하고, Codova로 받는 이벤트를 처리할 핸들러 들을 포함하고 있다. 앞서 만든 app에는 XplatApplication의 인스턴스의 참조를 저장한다.

그리고, app.initialize()를 실행하고 initialize()의 this.bindEvents()를 통해 Codova의 deviceready에 onDeviceReady와 여러 가지 앱 이벤트들을 등록하여 초기화한다. dataSource.refresh()하여 데이터를 새로 가져오고, WinJS.UI.processAll()을 실행하여 현재 페이지의 WinJS 컨트롤들의 바인딩이 처리되도록 한다.

todo.ts

dataSource는 todo.ts 파일에 정의가 되어 있다.

image

index.ts와는 같은 Xplat 네임스페이스를 공유하고 있다. dataSource는 ToDo 클래스 인스턴스 참조를 저장하기 위한 변수로 선언되어 있으며, 아래 부분에 ToDo 클래스를 정의하고 있다.

ToDo 클래스는 this._items에 WinJS.Binding.List 인스턴스를 생성하여 참조하고 있고, 앞서 index.ts에서 호출된 dataSource.refresh()도 여기 정의되어 있는 것을 확인할 수 있는데, Storage.getAllToDoItems()를 비동기 호출하고 결과가 왔을 때 this._items에 각 아이템을 this._createTodoBinding(items[i])로 처리하여 추가(push)하는 것을 볼 수 있다.

(XPlat.)Storage 모듈은 services.ts 파일에 정의되어 있는데, 앞선 AngularJS나 BackboneJS 샘플처럼 LocalStorage나 AzureStorage에 대한 구현과 Bing Maps API를 사용하는 부분의 코드가 들어있다.

비동기 호출 이후, 결과 데이터가 왔을 때 처리하는 _createToDoBinding() 코드를 잠깐 살펴보자.

image

WinJS.Binding.as()를 이용해서 바인딩할 수 있는 bindingItem 오브젝트를 만들고, changeText/toggleDone/remove 등의 변수에 람다식 문법(() =>{})을 이용하여 bindingItem 오브젝트과 함께 실행할 메서드들을 묶어서 이벤트 핸들러 함수를 만들고 이를 WinJS.UI.eventHandler()를 통해 html에서 사용할 수 있게 한다.

image

index.html의 템플릿 코드를 보면 위와 같이 onchange: changeText나 onclick: remove, onmousedown: toggleDone 핸들러가 지정되어 있는 것을 볼 수 있다. 각 bindingItem이 이 템플릿의 context가 되기 때문에 changeText는 bindingItem.changeText를 가르킨다.

services.ts

마지막으로 services.ts를 간단히 살펴보면 다음과 같은 클래스 구현을 볼 수 있다.

image 

TypeScript의 interface/implements 키워드를 이용하여 IStorageImplementation 인터페이스와 LocalStorage, WindowsAzureStorage의 클래스 구현을 만들고, storageImplementation 변수에 둘 중에 하나의 참조를 넣은 후, getAllTodoItems() 등의 함수에서 이를 사용하여 데이터에 대한 처리를 구현한다.

여기까지 간단히 WinJS와 TypeScript로 구현된 Codova 샘플을 살펴보았다.

AngularJS, BackboneJS, WinJS 등 3가지 하이브리드 앱 샘플을 살펴보면서 느낀 각각의 장단점을 간단히 정리하자면,

  • AngularJS – 바인딩과 html 확장이 편리하며 코드가 간결하다. 처음에 여러 가지 개념 및 스코프 동작 등의 개념을 이해하기 어렵고 JS 코드 가독성이 다소 떨어진다.
  • BackboneJS – 한 페이지에서 여러 화면을 가지는 애플리케이션 구현이 간단하다. HTML을 확장하지 않지만, 그로 인해서 JS에서 처리해야 하는 코드가 많아지고 MVC의 구분이 명확하지 않아서 코드가 지저분해 질 수 있다.
  • WinJS – 템플릿과 바인딩을 활용하여 코드를 간결하게 작성할 수 있고 앱 구현에 유용한 컨트롤이나 네비게이션 기능 등이 제공된다. 바인딩이나 UI 컨트롤이 동작하는 방식을 이해하기 어렵다.

TypeScript에 대해서는 모듈 기능이 따로 제공되지 않는 WinJS와 함께 사용하면 코드의 가독성을 상당히 높일 수 있어서 유용하고, AngularJS 등과도 함께 쓰면 좋겠지만 이미 모듈 기능을 제공하고 있으므로 함께 사용하는 방법을 좀 더 고민해 볼 필요가 있을 듯 하다.

윈폰용 텔레그램 앱의 UI 구조 살펴보기 #1

현재 참가 중인 앱 개발 모임에서 윈폰용 텔레그램 클라이언트인 Kilogram 오픈소스를 분석해 보고 있다. UI 부분의 분석을 맡기로 해서 간단히 살펴본 내용을 공유해 본다.

Entry point

우선 앱의 시작점은 Package.appxmanifest를 살펴보니, UI/Pages/StartPage.xaml로 되어 있다.

image

프로젝트의 UI 폴더와 Pages 폴더의 구성은 다음과 같다.

image
image

StartPage

StartPage.xaml에서 사용자 정의 Namespace를 살펴보면 다음과 같다.

image

이 중 controls의 경우는 DialogListControl과 UserListControl이 쓰이고 있다.

image

DialogListControl는 대화 목록이고, UserListControl은 연락처 리스트로 보인다.

image

그 밖에 프로젝트에서 사용하는 사용자 정의 컨트롤은 아래와 같다.

image

그 밖에 StartPage에는 앱바가 있는데, New, Search, Settings 등의 버튼이 있다. 각각 ChatCreate.xaml, SearchPage.xaml, Settings.xaml 페이지로 연결된다.

image

OnLoaded 이벤트에 아래와 같이 세션 인증이 됐는지를 따져서 안 된 경우 Intro.xaml을 보여준다.

image 

앞서 살펴본 2개의 리스트 컨트롤의 항목을 클릭했을 때에는 DialogPage로 보낸다.

image

단, 대화를 선택했을 때는 선택한 DialogModel 인스턴스를 TelegramSession.Instance.Dialogs.OpenedDialog에 넣고, 연락처를 선택했을 때는 userId를 페이지 인수로 전달한다.

참고로, UI폴더에 속한 Models 폴더의 클래스 구성은 단촐하다.(UI 폴더와 같은 레벨에 Models가 따로 있다.)

image

Intro & Signup

인트로 페이지에서 Signup 페이지로 전달되며, 여기서 Telegram.UI.Flows.Login을 이용해서 로그인을 한다.

image image

image

Login() 메서드에는 flow의 이벤트 처리 핸들러들이 정의되어 있고 마지막에 flow.Start()를 호출한다

image

Telegram.UI.Flows에는 Login 클래스만 존재한다.

image

로그인이 끝나면 다시 StartPage로 돌아간다.

ChatCreate

image

검색창, new group과 new secret chat이 있고, 연락처에 쓰인 UserListControl이 있다. 검색을 할 경우, UserControl에서 검색을 처리한다.

image

new group 버튼은 NewGroup.xaml 페이지로 보내고, new secret chat은 NewSecretChat.xaml로 보내며, UserList를 클릭한 경우는 StartPage와 마찬가지로 UserId를 껴서 DialogPage.xaml로 보낸다.

NewGroup과 NewSecretChat은 모두 UserListControl을 사용하고 있으며, NewGroup의 경우 그룹 이름과 멤버를 입력하는데 FormLatterEditPhoneControl을 사용한다.

image

image

두 페이지는 대화방 생성이 끝나면 StartPage로 보낸다. 아마 이렇게 생성된 그룹이나 대화방은 DialogList에 추가가 되는 것으로 보인다. 각각 TelegramSession.Instance.Api.messages_createChat(…)와 TelegramSession.Instance.EncryptedChats.CreateChatRequest(…)를 호출하여 대화방을 생성한다.

DialogPage

계속…

하이브리드 앱 개발 on Visual Studio #3

AngularJS 샘플에 이어서 BackboneJS 샘플을 살펴보고자 한다. 우선 샘플을 다운로드하여, 빌드를 해봤다.

  • AngularJS 샘플과 마찬가지로 framework JS파일들을 추가해 주어야 하고,
  • Bing Map API 키를 geolocation.js 파일에 넣고,
  • Azure API 키 및 Mobile Service URL을 services/mobile services/todolist-xplat/service.js 파일에 넣는다.

image

제대로 빌드했다면, 위와 같이 AngularJS 샘플에서 작성했던 Azure 상의 ToDo 항목들이 보인다.

BackboneJS의 기초

이번에는 BackboneJS의 기초를 먼저 살펴보고 샘플을 분석해 보는 것이 좋을 것 같다. 일단 튜토리얼은 여기에서 볼 수 있다.

아래 그림은 초보자용 영상에 나오는 샘플 앱의 구조인데, index.html은 Backbone Router를 통해 /, /edit, /new 등의 페이지 처리를 구현하고, Backbone의 View와 Model을 통해 REST API로 서버와 통신을 하게 된다. 영상에서는 아래 설계를 처음부터 하나씩 구현하여 간단한 사용자 관리 클라이언트를 만드는 과정을 보여주는데 다소 긴 영상이긴 하지만 꽤 볼만하다.

image

image 

위 코드는 튜토리얼에서 작성하는 코드의 일부인데, 살펴보면 Backbone. 으로 시작하는, Collection, View, Router 등의 클래스를 상속하여 사용자 정의 클래스들을 만들고 이를 통해 Router와 View, Model을 구현한다.

Router를 이용하여 페이지 네비게이션을 구현하는 부분이 편리해 보인다. router를 이용해 특정 페이지 요청이 있으면 해당 페이지에 연결된 View의 render() 메서드를 실행하여 화면을 그린다.

Backbone.View를 상속한 UserList의 경우는 Underscore의 _.template 함수로 Users Collection에서 가져온 users.models을 HTML 템플릿을 이용해 화면에 그려내고 있다.

BackboneJS 샘플 살펴보기

image

그럼 다시 샘플로 돌어가서 index.html 파일을 살펴보자. BackboneJS 샘플은 ToDo 목록의 templating을 위해서 underscore.js를 사용하고 있다.

    <!-- Refer to underscore.js for more information on its templating language -->
    <script type="text/html" id="todo-template">
        <div class="templateWrapper">
            <div class="templateContainer">
                <input class="templateTitle" type="text" value="<%- title %>" />
                <h3 class="templateAddress"><%- location %></h3>
                <button class="templateLeft templateToggle"></button>
                <button class="templateLeft templateRemove"></button>
            </div>
            <div class="templateBorder"></div>
        </div>
    </script>

underscore는 이름처럼 _.template(템플릿 문자열) 이런 식으로 사용한다. template 함수 관련해서는 여기를 참고하자. <%- title %> <%- location %> 등이 변수에서 값으로 치환되어 HTML로 생성된다.

index.html에서 template외에 나머지 부분에서 별다를 것은 없다.underscore는 HTML의 내장 오브젝트를 확장하지 않으면서 사용할 수 있는 여러 가지 함수형 프로그래밍 헬퍼들을 제공한다. 그런 면에서 ng-로 시작하는 속성이나 directive로 HTML를 적극 확장하는 AngularJS와는 다른 접근인 셈이다.

코드 살펴보기

index.js 파일을 살펴 보면 다음과 같다.

var app = app || {};

$(function () {
    "use strict";

    document.addEventListener('deviceready', onDeviceReady.bind(this), false);

    function onDeviceReady() {
        // Handle the Cordova pause and resume events
        document.addEventListener('pause', onPause.bind(this), false);
        document.addEventListener('resume', onResume.bind(this), false);
        // Initialize storage and the app's base view.
        app.initializeStorage();
        new app.BaseView();
    };

    function onPause() {
        // TODO: This application has been suspended. Save application state here.
    };

    function onResume() {
        // TODO: This application has been reactivated. Restore application state here.
    };
});

AngularJS 샘플과 달리, Codova에서 제공하는 앱에 대한 이벤트(deviceready, pause, resume)를 사용하고 있다. (AngularJS는 대신 초기화를 위해 html이나 body에 ng-app 속성을 지정하였다.) app.initializeStorage()는 storage.js 파일에, app.BaseView는 baseView.js 파일에 정의되어 있다.

storage.js 파일은 localStorage와 azureStorage 두 가지를 구현하고 mobileServiceClient의 사용 가능 여부에 따라 적절한 storage 구현을 app.Storage에 전달하는 역할을 한다.

baseView.js 파일은 Backbone.View를 상속하여 초기 화면을 구현하는 부분이다.

    app.BaseView = Backbone.View.extend({
        // This is the DOM element for the base view
        el: '#todoapp',

        // Hook up event handler to DOM keypress event
        events: {
            'keypress #new-todo': 'createNewTodo',
        },

        initialize: function () {
            // Retrieve DOM elements
            this.$input = this.$('#new-todo');
            this.$todoList = this.$('#todo-list');

            // Hook up some event handlers to the data source
            this.listenTo(app.todoCollection, 'add', this.addTodo);
            this.listenTo(app.todoCollection, 'reset', this.createNewTodosFromCollection);
            this.listenTo(app.todoCollection, 'all', this.render);

            // Retrieve existing data from storage
            app.Storage.getData();
        },

        // Create a new view from a todo and append it to our list.
        addTodo: function (todo) {
            var view = new app.TodoView({ model: todo });
            this.$todoList.append(view.render().el);
        },

el 속성에 #todoapp을 지정하여, index.html 파일에 “todoapp”이라는 id를 가진 <section> 태그를 baseView의 요소로 사용한다. 마우스 엔터키 이벤트를 keypress 이벤트로 가져와서 createNewTodo를 실행하고, app.todoCollection 데이터 소스의 add, reset, all 이벤트에 각각 핸들러를 추가하여 데이터에 변경사항이 있을 때 이를 처리하는 부분이 구현되어 있다. addTodo에는 app.TodoView를 생성하여 #todo-list 요소에 추가하고 있다.

todoView.js를 보면 다음과 같이 정의되어 있다.

app.TodoView = Backbone.View.extend({
        // This is the tag we wrap around our template
        tagName: 'div',

        // HTML item template
        template: _.template($('#todo-template').html()),

        // Hook up event handlers to mousedown and change events
        events: {
            'mousedown .templateToggle': 'toggleCompleted',
            'mousedown .templateRemove': 'deleteTodo',
            'change .templateTitle': 'updateTodo',
        },

        // Hook up event handlers to our model
        initialize: function () {
            this.listenTo(this.model, 'change', this.render);
            this.listenTo(this.model, 'todoDescriptionChanged', this.render);
            this.listenTo(this.model, 'destroy', this.remove);
        },

        // "Refresh" the todo in response to a change in the model
        render: function () {
            this.$el.html(this.getTemplateData());
            this.$input = this.$('.templateTitle');
            this.$toggle = this.$('.templateToggle');
            this.updateCrossOut();

            return this;
        },

        // We supply the model (as json) to instantiate our template
        getTemplateData: function () {
            return this.template(this.model.toJSON());
        },

template로 #todo-template를 사용하고 있고, 이건 앞서 index.html에서 확인하였다. render() 메서드를 baseView에서 호출하였으므로 살펴보면, getTemplateData()의 this.template(this.modeul.toJSON())을 통해서 todo 모델의 데이터가 적용된 HTML 템플릿을 가져와서 적용하고 리턴한다. $input, $toggle은 HTML 템플릿의 요소에 코드로 변경을 하기 위해 생성하는 참조이다. 리턴한 결과는 baseView에서 받아서 append한다.

todoCollection.js는 단순하다.

var app = app || {};

(function () {
    'use strict';

    var TodoCollection = Backbone.Collection.extend({
        model: app.TodoModel,
    });

    app.todoCollection = new TodoCollection;
})();

Backbone.Collection을 상속하고, model로 app.TodoModel을 지정한다.

app.TodoMobel은 todoModel.js 파일에 정의되어 있다.

    app.TodoModel = Backbone.Model.extend({

        defaults: {
            title: '',
            done: false,
            // This placeholder text is displayed while the app is querying the device's
            // location and supplying it to the restful service to obtain a street address.
            location: 'Getting your location...'
        },

        toggleCompleted: function () {
            this.save({
                done: !this.get('done')
            });
        },

        // We don't want to sync. We have a local/zumo storage implementation
        sync: function () { return false; }
    });

defaults에 title, done, location 등의 속성이 있고 toggleCompleted() 경우는 todoView에서 Todo 항목의 토글 버튼을 클릭했을 때 호출한다.

Todo를 추가하고 수정하는 등의 처리는 app.Storage를 통해서 하는데, 이러한 호출은 baseView(추가)와 todoView(수정, 삭제)에 있다. 아래 코드는 baseView의 createNewTodo 메서드이다.

createNewTodo: function (e) {
            if (e.which === ENTER_KEY && this.$input.val().trim()) {
                var todo = app.todoCollection.create({
                    title: this.$input.val().trim(),
                    done: false
                });

                // This callback will take data from the RESTful service, retrieve the street address,
                // and set it in the todo, then save it.
                var onSuccess = function (data) {
                    todo.save({
                        location: data.resourceSets[0].resources[0].address.formattedAddress
                    })

                    app.Storage.saveTodo(todo);
                };

                var onError = function (error, position) {
                    todo.save({
                        location: position.coords.latitude + "," + position.coords.longitude
                    })

                    app.Storage.saveTodo(todo);
                };

                // Retrieve the location data
                app.Geolocation.getLocation(onSuccess, onError).then(app.Geolocation.getAddressFromLocation);

                // Clear the input
                this.$input.val('');
            }
        }

살펴보면 app.todoCollection.create()를 이용해서 새로운 Todo 데이터를 만들고, 이를 app.Storage.saveTodo(todo)를 호출하여 서버 또는 로컬에 저장한다.

정리

여기까지 대략적인 BackboneJS 샘플의 구조와 코드를 살펴보았다.

  • Codova의 deviceready 이벤트를 받아서 app.Storage와 app.baseView를 초기화한다.
  • app.baseView는 Backbone.View를 상속받아서 구현하며, index.html의 <section id=”todoapp”>을 View로 사용한다.
  • app.baseView는 다시 app.todoView를 이용하여 각 todo 항목에 대한 UI 및 수정/삭제 등의 기능을 처리한다. 이 때 TodoView는 index.html의 todo-list-template을 가져와서 underscore.js의 template() 함수로 각각의 Todo 항목의 HTML을 생성하고 app.baseView에 리턴하여 append한다.
  • TodoModel과 TodoCollection은 각각 Todo 항목과 리스트를 가지고 있는데, BaseView와 TodoView는 사용자가 항목을 생성/수정/삭제할 때 TodoCollection에 반영하고, 이를 app.storage에 적용한다.
  • TodoCollection에 변경사항이 있을 때 이벤트는 다시 BaseView와 TodoView로 전달되어 화면을 업데이트한다.

BackboneJS는 처음 앱 구조를 봤을 때에 생각한 것에 비하여 이해하는데 어렵지 않았다. AngularJS처럼 이해해야 할 개념이 많지 않고 View/Collection을 꼭 다른 파일로 해야 하는 것이 아니라서 생각보다 쉽게 접근할 수 있었다. 다만, View가 UI와 데이터를 처리하는 부분까지 모두 포함하고 있는 부분이나 View가 여럿 있을 때 코드가 복잡해 질 수 있는 부분은 신경이 많이 쓰일 것 같다.

다음은 마지막으로 WinJS 샘플을 살펴보도록 하겠다.

하이브리드 앱 개발 on Visual Studio #2

이번에는 하이브리드 앱 샘플을 살펴보고자 한다. 샘플은 아래와 같이 3종류로 제공이 된다.

Angular나 Backbone은 자주 거론되는 프레임워크라 이번 기회에 간단히 살펴보았다.

AngularJS의 경우는 HTML의 선언적 코드 기능을 확장해서 (WinJS와 유사하게) ng-로 시작하는 속성들을 사용하여 JS 코드를 줄일 수 있고, 템플릿/리피터, MVC, 바인딩 등을 지원한다. 자세한 내용은 튜토리얼 참고.

HTML is great for declaring static documents, but it falters when we try to use it for declaring dynamic views in web-applications. AngularJS lets you extend HTML vocabulary for your application. The resulting environment is extraordinarily expressive, readable, and quick to develop.

BackboneJS는 MVC로 구조화된 웹앱을 만드는데 적합한 프레임워크이다. 이벤트, 모델, 콜렉션, 바인딩 등을 지원한다.

Backbone.js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.

두 가지가 비슷한 기능을 하지만 AngularJS 쪽은 편의성과 간결함에, BackboneJS는 웹앱을 구조화하는데 초점을 맞춘 느낌이다.

WinJS는 원래 HTML/CSS/JS 코드로 Windows 앱을 만들 때 필요한 유틸리티나 컨트롤, 바인딩 등의 기능을 사용할 수 있도록 제공되는 라이브러리인데, 최근 오픈소스로 Android, iOS, XBOX 등에서도 사용할 수 있게 하겠다는 발표를 했다. AngularJS처럼 HTML 확장 속성들을 제공하여 컨트롤을 사용할 수 있고 바인딩을 사용할 수 있다.

앱 구조 비교

그러면 이 3가지 프레임워크를 이용해서 만든 하이브리드 앱 샘플들을 살펴보자. 비교를 위해서 각 솔루션을 열고 파일 구조를 살펴보았다.

image 

images, merges, res 폴더에는 차이가 없고, css의 경우는 WinJS에서만 기본 제공되는 2가지 테마 파일이 추가되어 있다.

scripts 폴더를 보면 frameworks에 각 프레임워크 별로 필요한 파일들이 들어 있다. BackboneJS의 경우는 jQuery를 포함하고 있는데, IE에서 DOM Manpulation 이슈 때문에 jQuery나 Zepto, JBone 등을 써야 한다고 한다.(참고)

AngularJS 샘플은 controllers.js, directives.js, service.js(guidGenerator, localStroage,  azureStorage, storage, maps, codova 관련 코드)등으로 구성되어 있다.

BackboneJS 샘플은 명시적으로 모델과 뷰 파일(-Model.js, –View.js) 및 storage.js(Local Storage와 Azure Mobile Service 사용 코드), baseView.js, geolocation.js, services 폴더 및 js 파일(Azure 모바일 서비스 초기화) 등으로 구성된다.

WinJS 샘플은 특이하게 TypeScript를 사용하고 있는데, 기본 TypeScript 하이브리드 앱 프로젝트에 포함되는 typings 폴더가 있고, mobileservices.d.ts, winjs.d.ts(이상 형 선언 파일), services.ts(Storage, Azure Mobile Service, Maps, guid 등 코드), todo.ts(ToDo 리스트 구현) 등이 있다.

파일 구조만 보면 Angular.js가 가장 깔끔해 보이지만, WinJS 프로젝트에서 TypeScript의 선언 파일들을 제외하면 index.ts + services.ts + todo.ts로 좀 더 간결하다. BackboneJS의 경우는 MVC가 명시적으로 나눠져 있고 구조화된 느낌이라 다소 복잡해 보인다.

빌드하기

프로젝트를 빌드/실행하려면 몇 가지 작업을 해야 한다. 샘플에 포함된 Readme.txt을 읽어보면 다음과 같다.

  1. Bing Maps API key 생성 및 코드 수정
  2. Microsoft Azure API key 생성 및 코드 수정, 아래와 같은 schema로 DB에 테이블 생성(선택사항, 없을 경우 LocalStorage에 저장)
    • (id:string, __createdAt:date, __updatedAt:date, __version:timestamp, text:string, done:boolean,

      address:string) 이 테이블은 각 Mobile Service의 Get Started 화면에서 원 크릭으로 생성할 수 있다.

  3. 실행하면 Powershell script가 실행되면서 솔루션에서 빠져있는 의존 라이브러리 파일들을 다운로드 받음(위 솔루션 탐색기 화면에서 Missing 표시 있는 것들. 자동 다운로드는 잘 안 됐는데 직접 다운로드해서 넣었다.)

먼저, Bing Maps API key는 여기서 만들 수 있고, Azure는 여기서 가입 및 Mobile Service 생성을 할 수 있다. Mobile Service 생성은 튜토리얼 참고.

image

Microsoft Azure Mobile Service에서 TodoItem 테이블 생성 화면

AngularJS 앱

AngularJS 앱부터 천천히 살펴보도록 한다. AngularJS에 필요한 framework 폴더의 JS파일들은 여기서 다운로드 할 수 있다. 앞서 API Key와 Mobile Service URL을 services.js 파일에 넣고 JS파일을 추가한 다음 실행해 보았다. 다음은 Ripple 에뮬레이터에서 실행 화면이다.

image 

Azure Mobile 서비스와 연동하여 잘 실행되는 것을 확인할 수 있다. Azure 포털에서 TodoItem DB 데이터를 확인해 보면 아래와 같이 앱에서 실행한 내용이 적용되어 있는 것을 확인할 수 있다.image

AngularJS 앱의 services.js 파일에 Azure 사용 부분은 다음과 같이 되어 있다.

(function () {
    'use strict';

    angular.module("xPlat.services")
...
        .factory("azureStorage", ["$q", "$resource", "$rootScope", "guidGenerator", function ($q, $resource, $rootScope, guidGenerator) {
            var azureMobileServicesInstallationId = guidGenerator();
...
	    var azureStorage = {
                getAll: function () {
                    return toDoItem.query();
                },

                create: function (text, address) {
                    var item = new toDoItem({
                        text: text,
                        address: address,
                        done: false
                    });

                    return item.$save();
                },

                update: function (item) {
                    return item.$update();
                },

                del: function (item) {
                    return item.$delete();
                },
            };

            Object.defineProperty(azureStorage, "isAvailable", {
                enumerable: false,
                get : function(){ return azureMobileServicesKey && azureMobileServicesAddress; },
            });

            return azureStorage;
        }])
...

AngularJS는 .module()로 기능들을 모듈화하는 것을 권장하고 있다. 모듈은 몇 가지 메서드를 갖는데 factory()는 Factory 패턴을 사용하여 지정한 service 클래스(여기서는 azureStorage)의 인스턴스를 생성해주는 역할을 한다.(module과 factory에 대한 설명은 여기, angular.module 레퍼런스는 여기 참고) 여기서는 getAll, create, update, del 메서드를 가진 azureStorage 인스턴스를 생성해서 리턴한다. localStorage도 동일하게 factory()로 구현이 되어 있고, 아래와 같이 storage 팩토리에서 Azure와 localStorage 중에 선택하여 리턴하는데 사용된다. 

.factory("storage", ["$injector", function($injector) {
            var azureService = $injector.get('azureStorage');
            return azureService.isAvailable ? azureService : $injector.get('localStorage');
        }])

원래 factory() 메소드는 factory(name, providerFunction) 이렇게 구성되는데, providerFunction을 [“$injector”, function($injector) {…}] 와 같이 전달하면 해당 scope의 변수들을 함수 레퍼런스와 함께 인수로 전달한다. azureStorage 또는 localStorage의 인스턴스를 가져오기 위해 $injector.get(name)을 사용하고 있다. 이렇게 리턴한 인스턴스는 controller.js 파일에서 사용된다. controller.js 파일은 index.html 파일을 살펴보면서 다시 보자.

AngularJS는 코드는 간결하지만 모듈이나 팩토리 같은 개념들을 이해하지 않고 접근하기에는 다소 어려운 듯 하다. 기본 개념들은 여기를 참고하면 좋을 듯.

코드 살펴보기

AngularJS는 HTML을 확장해서 사용하므로 HTML 코드도 같이 살펴볼 필요가 있어 보인다. 다음은 index.html의 body 태그 부분이다.

<body ng-app="xPlat">
    <section id="todoapp" ng-controller="ToDoCtrl">
        <header id="header">
            <div id="headerBand"></div>
            <input id="new-todo" placeholder="What needs to be done?" ng-text-change="addToDo()" ng-model="newToDoText" autofocus>
        </header>
        <section id="main">
            <div id="todo-list">
                <div class="templateWrapper" ng-repeat="toDoItem in todos">
                    <div class="templateContainer">
                        <input class="templateTitle" ng-class="{crossedOut: toDoItem.done}" type="text" ng-text-change="changeToDoText(toDoItem)" ng-model="toDoItem.text" />
                        <h3 class="templateAddress">{{toDoItem.address}}</h3>
                        <button class="templateLeft templateToggle" ng-class="{'checked': toDoItem.done, 'unchecked': !toDoItem.done}" ng-mousedown="toggleToDoDone(toDoItem)"></button>
                        <button class="templateLeft templateRemove" ng-click="removeToDo(toDoItem)"></button>
                    </div>
                    <div class="templateBorder"></div>
                </div>
            </div>
        </section>
    </section>

    <script src="scripts/index.js"></script>
    <script src="scripts/services.js"></script>
    <script src="scripts/controllers.js"></script>
    <script src="scripts/directives.js"></script>
</body>

body의 ng-app 값이 xPlat으로 설정되어 있다. ng-app은 html이나 body 태그에서 AngularJS 앱이라는 것을 나타내는 지시어이고, xPlat는 index.js에서 다음과 같이 생성하고 있다.

(function() {
    'use strict';
    angular.module('xPlat', ['xPlat.services', 'xPlat.controllers', 'xPlat.directives']);
    angular.module('xPlat.directives', []);
    angular.module('xPlat.controllers', []);
    angular.module('xPlat.services', ['ngResource']);
})();

‘xPlat’ 뒤의 […] 부분은 dependency를 나타낸다. directives, controllers, services 등의 하위 모듈들을 불러온다. index.html에서 <section> 요소에 속성으로 지정한 ng-controller=”ToDoCtrl”은 Todo 리스트로 동작하게 하기 위해 ToDoCtrl이라는 컨트롤러를 지정하고 있다. 이는 controller.js 파일에 xPlat.controllers 모듈로써 구현되어 있다. 앞서 살펴보았던 storage가 여기에서 사용된다. ToDoCtrl의 providerFunction의 인자로 전달하는 maps와 storage는 services.js 파일에 xPlat.services의 각 모듈로 구현되어 있다.

    angular.module("xPlat.controllers")
        .controller('ToDoCtrl', ['$scope', 'maps', 'storage', function ($scope, maps, storage) {
            var refresh = function() {
                $scope.todos = storage.getAll();
            }

            var getAddress = function() {
                return maps.getCurrentPosition()
                    .then(maps.getAddressFromPosition, function(error) { return error.message; });
            }

            $scope.addToDo = function () {
                var text = $scope.newToDoText;
                if (!text) {
                    return;
                };

                $scope.newToDoText = '';
                getAddress().then(
                    function (address) { return storage.create(text, address); },
                    function (errorMessage) { return storage.create(text, errorMessage); })
                .then(function (todo) {
                    $scope.todos.push(todo);
                });
            }
...

index.html의 <input> 태그에 보면 ng-text-change=”addTodo()”와 ng-model=”newTodoText”가 있는데, 각각 위 코드 상의 ToDoCtrol 컨트롤러에 정의 되어 있는 $scope.addTodo()와 $scope.newTodoText를 가르킨다. ($scope에 대한 내용은 여기 참고). ng-model은 <input> 태그와 addToDo()에서 호출하는 $scope.netToDoText와 바인딩을 시키는데 사용하고, ng-text-change는 개발자가 지정한 directive로 directives.js 파일에 아래와 같이 설정되어 있다.

angular.module('xPlat.directives')
        .directive('ngTextChange', function() {
            return {
                restrict: 'A',
                replace: 'ngModel',
                link: function(scope, element, attr) {
                    element.on('change', function() {
                        scope.$apply(function() {
                            scope.$eval(attr.ngTextChange);
                        });
                    });
                }
            };
        });

“ng-text-change”라는 속성(A)이 있을 시에 할당된 함수를 해당 요소의 ‘change’ 이벤트에 핸들러로 지정하도록 되어 있다. AngularJS의 자세한 directive 용법은 여기를 참고하자.

굳이 change를 쓰지 않고 ng-text-change를 정의한 이유는 다음에 나오는 리스트에서 각 항목의 제목을 수정했을 때에도 똑같이 사용하기 때문인 것으로 보인다. index.html에 다음과 같은 부분이 있다.

<div id="todo-list">
                <div class="templateWrapper" ng-repeat="toDoItem in todos">
                    <div class="templateContainer">
                        <input class="templateTitle" ng-class="{crossedOut: toDoItem.done}" type="text" ng-text-change="changeToDoText(toDoItem)" ng-model="toDoItem.text" />
                        <h3 class="templateAddress">{{toDoItem.address}}</h3>
                        <button class="templateLeft templateToggle" ng-class="{'checked': toDoItem.done, 'unchecked': !toDoItem.done}" ng-mousedown="toggleToDoDone(toDoItem)"></button>
                        <button class="templateLeft templateRemove" ng-click="removeToDo(toDoItem)"></button>
                    </div>
                    <div class="templateBorder"></div>
                </div>
            </div>

todos에 있는 내용을 리스트 형태로 보여주는 template이다. 첫 번째 <input> 요소에 ng-text-change=”changeToDoText(toDoItem)” 이 있는 것을 확인할 수 있다. todos는 controller.js 파일에 refresh() 함수에서 storage.getAll()을 통해 불러오고 있다.

정리

정리를 해보면,

  • index.html에서 ng-app으로 AngularJS앱으로 실행하고 index.js에서 각 모듈을 초기화한다.
  • directives.js에서 index.html의 <input> 태그에 ng-text-change 속성을 설정하고, 값이 변경될 때 controller.js의 addToDo()를 실행한다.
  • addToDo()는 services.js의 storage 팩토리를 통해서 localStorage 또는 azureStorage를 가져오고, storage.create()로 해당 ToDo 아이템을 저장한다.
  • 현재 저장된 ToDo 리스트는 controller.js에서 refresh()->storage.getAll()로 가져오며, AngularJS의 template으로 뿌려준다.

 

여기까지 AngularJS 샘플을 간단히 살펴보았다. BackboneJS나 WinJS 등도 함께 살펴보고자 했는데, 내용이 워낙 많아서 하나의 포스트에서 모두 다루기는 어려워 보인다. 다음 글에서 Backbone.js와 WinJS를 살펴보도록 하겠다.

하이브리드앱 개발 on Visual Studio #1

페이스북에서 아래와 같은 소식이 잠시 화제가 되었다.

image

한 장의 스크린샷과 함께, 안드로이드SDK/크롬SDK/Git/SQL를 지원할 예정이라는 내용이었다. 잘못 알려진 부분이 있어서 좀 더 자세히 다뤄보고자 한다.

일단 마이크로소프트는 “비주얼 스튜디오에서 안드로이드SDK, 크롬SDK, Git, SQL 지원 예정”이라는 발표를 한 적이 없다. 대신 최근에 Apache Cordova와 함께 하이브리드 앱 개발을 지원하겠다고 발표하였다. 현재 이 기능은 프리뷰 버전으로 제공되고 있다.

하이브리드앱은 앱 내에서 웹 컨텐츠를 처리하기 위해서 간단한 로컬 웹서버/웹뷰를 필요로 한다. 또한, 웹 컨텐츠에서 디바이스 기능을 호출할 수 있는 API와 각 플랫폼 별로 앱 패키징해주는 기능이 필요하다. 이러한 기능을 하는 것이 Apache Cordova이다. 원래는 PhoneGap이라는 이름으로 유명하지만, Adobe에서 이를 오픈소스화하여 코드 베이스를 Apache로 넘겨서 Apache Cordova가 되었다고 한다. 두 프로젝트는 현재 이름만 다른 패키지 배포판이다. (참고)

그렇다면, VS에서 하이브리드 앱을 지원하기 위한 기술 요소들을 한번 생각해보자. 먼저, Android나 iOS 개발도구가 필요하다. 각 앱을 빌드하고 에뮬레이터(Android의 경우)를 띄워서 앱을 실행할 수 있어야 하기 때문이다. Android 앱을 빌드하려면 Java도 필요하고, Webkit 기반의 웹뷰를 에뮬레이팅하기 위해 Chrome이 필요할 것으로 보인다. SQLite는 WebSQL를 대신해서 로컬 스토리지 용도로 쓰일 수 있다. 그리고, iOS 앱을 배포하기 위해서 iTunes가 필요할 것으로 보인다.

실제로, Multi-Device Hybrid Apps과 함께 제공되는 문서에 보면 이러한 내용이 명시되어 있다.

The installer will then ask permission to download certain dependencies. These are mostly open source software pre-requisites required by individual platforms or Apache Cordova to build and run your applications. Any dependencies that already exist on your system will not be re-installed (as long as the required version is present).

하이브리드 앱을 빌드하기 위해 필요한, 개별 플랫폼이나 Apache Cordova에 필요한 오픈소스 구성요소들을 설치한다고 되어있다. 좀 더 자세하고 정확한 설명은 다음과 같다.

Third Party Dependencies 

  • Joyent Node.js – Enables Visual Studio to integrate with the Apache Cordova Command Line Interface (CLI) and Apache Ripple™ Emulator
  • Git CLI – Required only if you need to manually add git URIs for plugins
  • Google Chrome – Required to run the Apache Ripple emulator for iOS and Android
  • Apache Ant 1.8.0+ – Required as a dependency for the Android build process
  • Oracle Java JDK 7 – Required as a dependency for the Android build process
  • Android SDK – Required as a dependency for the Android build process and Ripple
  • SQLLite for Windows Runtime – required to add SQL connectivity to Windows apps (for the WebSQL Polyfill plugin)
  • Apple iTunes – Required for deploying an app to an iOS device connected to your Windows PC

앞서 페이스북의 소식에 지원 예정이라고 되어 있었던, GitSQLite의 경우는 이전 Visual Studio 2012에서 부터 사용할 수 있었다.

이왕 살펴보는 겸, VS에서 하이브리드 앱을 어떻게 만드는지 좀 더 살펴보자.

image

VS 2013 Update 2부터는 TypeScript가 정식으로 지원되어, 위 화면과 같이 하이브리드 앱을 만들 때에도 JavaScript와 TypeScript 프로젝트 템플릿이 제공된다.

프로젝트 구성은 다음과 같다.(TypeScript의 경우)

image

여기서 살펴볼 만한 부분은 res, typeings, merges 정도이다.

res는 플랫폼 고유 리소스들이 들어 있는 폴더이다. 서명, 아이콘, 스플래시 스크린 등이 들어간다. 그리고, merges는 CSS 파일과 같은 플랫폼 고유의 코드를 추가하는데 사용한다.

기본 문서에는 JS로 되어 있으나, 호기심에 TypeScript(이하 TS)로 해보았다. JS 프로젝트와의 차이는, scripts 폴더에 index.js 대신 index.ts 파일이 있으며, 추가로 typings 폴더가 있다는 점이다. JS 프로젝트에는 typings 폴더가 없다. typings 폴더 밑에는 Cordova를 구성하는 각 클래스 별로 Type이 선언되어 있는 *.d.ts 파일이 있다.

image

다른 JavaScript 라이브러리의 TypeScript에서 사용하기 위해서 사용하는 파일들이다. Cordova의 API를 사용할 때 Type 체크나 IntelliSense 등에 사용된다. 자세한 내용은 이 글의 Ambient Declaration 부분이나 TypeScriptLang.org의 내용을 참고하자.

일단 비어있는 템플릿 앱을 실행해 보았다.

image

실행하는 옵션에 Android 에뮬레이터와 디바이스가 보인다. 그런데 실행하자 다음과 같은 에러가 나온다.

image

해당 에러를 검색해보면 스택오버플로우에 답변이 있다. 환경 변수와 PATH 설정이 잘 못 되어 있어서 그런 듯 하다. 가이드 대로 환경 변수와 PATH 설정을 변경하고 재시작하면, Ripple 에뮬레이터에서 실행하는데에는 더 이상 문제가 발생하지 않는다.

흥미로운 점은, Ripple이라는 하이브리드 앱의 에뮬레이터이다.

image

하이브리드 앱의 에뮬레이터가 Chrome 브라우저에서 실행되고 있다. 앞서 설치 과정에서 Chrome을 왜 설치하는지 알 수 있었다.

마지막으로 Device/Emulator에서 앱을 실행해보려고 했으나 위와 유사한 또 다른 에러가 발생했다. 내 경우에는 Android ADK 가 2곳에 설치가 되어 있었는데 예전에 설치했던 ADK의 버전이 낮아서 발생하는 문제인 것 같았다. Android SDK Manager에서 API 레벨을 19이상으로 업데이트해서 문제를 해결하고 다음과 같이 디바이스에서의 실행에 성공하였다.

_978

여기까지 Multi-Device Hybrid Apps 공식 가이드 문서를 참고로 하여 기본적인 하이브리드 앱 개발환경을 살펴보았다. 사실 개인적으로는 WinJS 라이브러리를 타 플랫폼에서 어떻게 사용할 수 있는지가 궁금한데, 이 부분은 다음 편에서 WinJS 앱 샘플을 통해 살펴보도록 하겠다.