레이블이 Android Studio인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Android Studio인 게시물을 표시합니다. 모든 게시물 표시

2019년 12월 16일 월요일

[AndroidStudio] 빌드까지 정상적으로 되는데 라이브러리를 찾을수 없다는 오류 날때

요즘 틈틈히 안드로이드 스튜디오로 앱을 만드는 공부를 하고 있다. 

그런데, 불과 몇주전에 참고하던 소스를 새로 열어보았더니 온통 에러 경고로 에디터창이 빨간색 투성이가 아닌가? 

Cannot resolve symbol 'AppCompatActivity'

이건 라이브러리를 못찾아서 나는 오류가 아닌가?
그런데, 잘되던 소스에 아무것도 하지 않았는데 왜 오류가 나는거지?


문제는 오류라고 표시되어 있는 곳의 에러메시지를 보면 에러가 날만한 곳이 아닌데 오류라고 표시되어 있는것이다.

보통 이런 경우는 버전 기록 오류가 많지만, 이 소스는 얼마전까지 정상적으로 실행되었던 소스고 그 뒤로 손도 대지 않았다. 그런데 오류가 난다.

심지어 컴파일 하고 실행해보면 정상적으로 실행까지 다 된다.

황당하지만, 당황하지 않고 구글신께 여쭤보았더니 친절한 구글신께서 답을 알려 주셨다.

우선 안드로이드 스튜디오를 종료한다.


그런다음, 해당 프로젝트의 디렉토리에 보면 ".idea" 라는 폴더가 있는데 이 폴더를 열어본다.


이 폴더에 "libraries" 라는 폴더를 삭제하거나 이름을 변경하면 된다.
그리고 다시 안드로이드 스튜디오를 실행.
그러면 안드로이드 스튜디오가 프로젝트를 불러오며 다시 "libraries" 폴더를 만들어 준다.


이렇게하면 이렇게 아무것도 하지 않았는데 정상적으로 오류가 났던 라이브러리를 불러 오는것을 볼수 있다.

아까 "libraries" 폴더의 이름을 바꿨던 위치에 가보면 새로운 "libraries" 폴더가 생성되어 있다.

예전에 사용하던 이클립스도 이렇게 기존 파일이 꼬여서 문제가 없는데도 문제가 있는것처럼 보여주는 경우가 많았는데 안드로이드 스튜디오도 그렇다. 좀더 기술이 발전 해서 이런 어처구니 없는 오류는 없었으면 좋겠다.

2019년 5월 14일 화요일

Flutter : 플러터 설치

언제나 하나의 프로그램으로 모든 장치에서 다 실행되는 꿈 같은 개발 프로그램이 나오길 기다린다.

그런 면에서 요즘 핫 한 개발툴이 플러터(Flutter).
그래서 한번 설치해 보기로 했다.

배포 사이트 : https://flutter.dev/docs/development/tools/sdk/releases#windows

배포 사이트에서 일단 SDK 를 다운 받자.


일단은 윈도우 버젼으로. 그리고 안전한 "Stable" 버젼을 다운 받자.


다운 받으면 400메가 쯤되는데, 따로 설치가 설치하지 않으므로 그냥 사용할 위치에 압축을 풀어 주면된다.

다운 받은 파일은 SDK 일 뿐이므로, 실제로는 플러터로 개발하려면 안드로이드 스튜디오 같은 별도의 개발툴을 사용해서 개발해야 한다. 여기서는 이미 설치되어 있는 "안드로이드 스튜디오" 를 사용할 예정.


압축 푼 파일에서 "flutter_console.bat" 를 실행하자.


아마 윈도우10 이면 경고 창이 뜰 것이다. 플러터 SDK 배포터는 일단 믿을 수 있는 사이트지만, 믿고 말고는 개인의 판단이니 알아서 결정하시면 되겠다. 만약 믿음으로 받아 들이고 설치를 하겠다면, 하단에 "추가 정보" 항목을 클릭해 보자.


그러면 위험함에도 불구하고 "실행" 하겠다는 버튼이 있으니, 이것으로 실행한다.


드디어 나타난 플러터 설치 창...


일단 "flutter doctor" 를 실행 시켜 현재 시스템에 플러터가 설치 가능한지를 체크한다.
일반적으로 당연히 문제가 있다고 나오니, 당황하지 않고 뭐가 문제인지 하나하나 해결 하면 된다.

문제는 나의 경우 "Unable Android SDK" 오류가 난다는것.
일반적으로는 "안드로이드 스튜디오"가 설치되어 있으면 이런 오류가 나지 않는다.


하지만, 난 이미 안드로이드 스튜디오가 설치되어 있는데?


당황하지말고, 고급 시스템 설정으로 이동하자.
이것은 안드로이드 SDK 경로가 지정되어 있지 않기 때문에 발생하는 오류이다.


여기서 "환경변수".


여기에다 "ANDROID_HOME" 항목을 추가 해주면된다.


만약 안드로이드 스튜디오를 설치한지 오래되어 안드로이드 SDK 의 경로가 생각나지 않는다면, 안드로이드 스튜디오 설정에서 찾을수 있다.


찾은 안드로이드 SDK 경로를 "ANDROID_HOME" 항목으로 등록하자.


시스템의 "path" 항목에도 해당 경로를 추가해줘야 한다는 것을 잊지 말자.


OK.

안드로이드 SDK 가 정상적으로 인식되었다.

하단에 보니 라이센스 문제가 있는가 본데, 시키는 대로 "flutter doctor --android-licenses" 라고 입력하자.


그러면 뭔가 많은 양의 텍스트가 나오는데 하나하나 읽어 보던가, 아니면 그냥 "Y" 눌러 주고 넘어가자.


이제 라이센스 문제도 해결되었고 마지막으로 안드로이드 스튜디오에 "flutter" 와 "Dart" 플러그인이 설치되어 있지 않다는 문제만 남았다.


요건 환경설정으로 이동해서...


플러그인 > flutter 검색 > flutter install.


flutter 플러그인은 Dart 프로그램 언어로 작동하므로 flutter 를 설치하면 자동으로 Dart 도 같이 설치된다. 


요렇게 설치하면 플러그인 문제는 단숨에 해결되고, 드디어 모든 문제가 해결되었다.
이걸로 flutter 설치 완료!!!

참고로 나의 경우 제일 하단의 "Connected device" 가 초록색으로 체크되어 있는데, 이것은 내 컴퓨터에 개발용 폰이 연결되어 있기 때문에 통과로 체크 된것이다. 만약 설치 컴퓨터에 개발용 폰이 연결되어 있지 않으면 노란색 경고로 체크되는데, 그렇더라도 개발에는 문제 없으니 그냥 넘어 가도 된다.


진짜로 되는가 테스트해보기 위해 안드로이드 스튜디오의 "새 프로젝트" 항목에 보면 "Start a new Flutter project" 라는 항목이 추가되어 있는 것을 볼 수 있다.


테스트용이니 일단 아무거나 선택하고...


중간에 flutter SDK 경로를 지정하는 부분이 나오는데, 이부분만 지정해 주면 나머지는 딱히 수정하지 않아도 된다.


이렇게 프로젝트를 생성하면...


쨘~~~.
flutter 프로젝트 가 만들어 졌습니다.

여기서 상단의 실행 버튼을 이용해 실행 해보자.


OK~~~
이렇게 쉽고 간단하게 flutter 프로그램을 만들 수 있다.


2016년 5월 30일 월요일

Android Studio : 커스텀 뷰로 리스트 뷰 만들기

일단 이전에 "웹페이지 읽어오기" 상태를 기준으로 설명한다.

웹페이지 읽어오기 : http://son10001.blogspot.kr/2016/05/android-studio_30.html

요 상태에서...


커스텀 뷰 레이아웃을 하나 만들자.
레이아웃 항목에서 "Layout resource File" 메뉴를 선택해 파일을 만든다.
(물론 이건 조금이라도 타이핑 할 내용을 줄이기 위해서 하는거라서, 그냥 직접 XML 레이아웃을 다 쳐 넣어도 된다.)


만들 레이아웃 파일명을 넣고 확인.


그럼 이렇게 레이아웃 파일이 생성된다.


커스텀 레이아웃에 쓸 아이콘 이미지를 몇개 추가 한다.
아이콘 이미지 : 




완성된 레이아웃은 대충 이런 형태.
메인 타이틀, 서브 텍스트, 아이콘 이미지. 이렇게 3개의 항목이 하나의 셋인 리스트 뷰다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="50dip"
        android:layout_height="50dip"
        android:id="@+id/imageView"
        android:src="@drawable/num01"
        android:layout_margin="5dip" />

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Title Text"
            android:id="@+id/textView"
            android:textSize="24dip"
            android:layout_marginLeft="5dip"
            android:layout_marginTop="5dip"
            android:layout_marginRight="5dip"
            android:layout_marginBottom="1dip" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Sub Text"
            android:id="@+id/textView2"
            android:textSize="16dip"
            android:layout_marginLeft="5dip"
            android:layout_marginTop="1dip"
            android:layout_marginRight="5dip"
            android:layout_marginBottom="5dip" />

    </LinearLayout>

</LinearLayout>

3개의 항목이 하나의 아이템으로 동작 할 것이므로, 클래스를 만들어 묶어 준다.


클래스를 추가하고.


이름을 적당하게 지정한다.


데이터 셋을 관리할 클래스이므로 이렇게 Getter / Setter 를 구현 해줌.

package com.son10001.formbasic;

public class ListItem {
    int image;
    String title;
    String sub;

    public ListItem ( int inImage, String inTitle, String inSub){
        this.image = inImage;
        this.title = inTitle;
        this.sub = inSub;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getSub() {
        return sub;
    }

    public void setSub(String sub) {
        this.sub = sub;
    }

    public int getImage() {
        return image;
    }

    public void setImage(int image) {
        this.image = image;
    }

}


그 다음에는


어뎁터를 구현해 준다.

참고 : http://son10001.blogspot.kr/2016/05/android-studio-listview.html

이전에 만들었던 리스트 뷰와 같은 방식으로 만들면 되는데, 단지 getView 가 좀더 복잡하고 addItem 이 데이터 셋을 넘겨서 여러 아이템을 한꺼번에 적용하는게 다르다.

이렇게 어뎁터 까지 구현해 줬으면...


이젠 메인 액티비티에서 써 먹으면 된다.

package com.son10001.formbasic;

import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.util.ArrayList;

public class BasicActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_basic);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        ListView listView = (ListView) findViewById(R.id.listView);
        MyAdapter adapter = new MyAdapter(this);
        listView.setAdapter(adapter);


        ArrayList<ListItem> items = new ArrayList<ListItem>();
        items.add(new ListItem(R.drawable.num01, "Title 01", "Sub 01"));
        items.add(new ListItem(R.drawable.num02, "Title 02", "Sub 02"));
        items.add(new ListItem(R.drawable.num03, "Title 03", "Sub 03"));
        items.add(new ListItem(R.drawable.num04, "Title 04", "Sub 04"));

        adapter.addItem(items);


        // 클릭이벤트
        findViewById(R.id.btnGetWeb).setOnClickListener(mClickListener);

    }





    Button.OnClickListener mClickListener = new View.OnClickListener() {
        public void onClick(View v) {
            try {
                new HttpUtil().execute();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

    private URL Url;
    private String strUrl,strCookie,result;

    public class HttpUtil extends AsyncTask<Void, Void, Void> {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            strUrl = "http://192.168.0.181:916/gettest.aspx";
        }

        @Override
        protected Void doInBackground(Void... voids) {
            try{
                Url = new URL(strUrl);
                HttpURLConnection conn = (HttpURLConnection) Url.openConnection();
                conn.setRequestMethod("GET");
                conn.setDoOutput(true);
                conn.setDoInput(true);
                conn.setUseCaches(false);
                conn.setDefaultUseCaches(false);

                strCookie = conn.getHeaderField("Set-Cookie");
                InputStream is = conn.getInputStream();

                StringBuilder builder = new StringBuilder();
                BufferedReader reader = new BufferedReader(new InputStreamReader(is,"UTF-8"));
                String line;

                while ((line = reader.readLine()) != null) {
                    builder.append(line+ "\n");
                }

                result = builder.toString();
            }catch(MalformedURLException | ProtocolException exception) {
                exception.printStackTrace();
            }catch(IOException io){
                io.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            System.out.println(result);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_basic, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}



이렇게 하고 앱을 실행 해 보면...


요렇게 이쁜 리스트 뷰가 생성된다.

Android Studio : 웹페이지 읽어 오기.

안드로이드에선 http 통신으로 웹페이지를 읽어오는 방법이 많이 있다.
대부분은 외부 라이브러리를 사용하는 방법인데, 그만큼 기본으로 제공하는 기능이 많이 빈약하기 때문.

하지만, 가급적이면 외부 라이브러리 없이 구현.

일단 기본 틀은 이전에 작업했던 폼을 사용.

리스트 뷰 사용 : http://son10001.blogspot.kr/2016/05/android-studio-listview.html


여기다 버튼을 하나 추가해서 기능을 구현해 본다.


버튼을 하나 추가해 놓고...


메인 액티비티에 클릭 이벤트 등록해 놓고...


클릭 이벤트 리스너와 웹 페이지 조회 루틴 작성.


package com.son10001.formbasic;

import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;

public class BasicActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_basic);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        String[] names = {"Item01", "Item02", "Item03", "Item04", "Item05"};
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, names);

        ListView listView = (ListView) findViewById(R.id.listView);
        listView.setAdapter(adapter);

        // 클릭이벤트
        findViewById(R.id.btnGetWeb).setOnClickListener(mClickListener);

    }

    Button.OnClickListener mClickListener = new View.OnClickListener() {
        public void onClick(View v) {
            try {
                new HttpUtil().execute();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

    private URL Url;
    private String strUrl,strCookie,result;

    public class HttpUtil extends AsyncTask<Void, Void, Void> {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            strUrl = "http://192.168.0.181:916/gettest.aspx";
        }

        @Override
        protected Void doInBackground(Void... voids) {
            try{
                Url = new URL(strUrl);
                HttpURLConnection conn = (HttpURLConnection) Url.openConnection();
                conn.setRequestMethod("GET"); // get방식 통신
                conn.setDoOutput(true); 
                conn.setDoInput(true);
                conn.setUseCaches(false);
                conn.setDefaultUseCaches(false);

                strCookie = conn.getHeaderField("Set-Cookie");
                InputStream is = conn.getInputStream();

                StringBuilder builder = new StringBuilder();
                BufferedReader reader = new BufferedReader(new InputStreamReader(is,"UTF-8"));
                String line;

                while ((line = reader.readLine()) != null) {
                    builder.append(line+ "\n");
                }

                result = builder.toString();
            }catch(MalformedURLException | ProtocolException exception) {
                exception.printStackTrace();
            }catch(IOException io){
                io.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            System.out.println(result);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_basic, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}


여기까지 작성되었으면...
AndroidManifest.xml 에 인터넷 접속 가능 속성 지정.


<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />


이제 실행을 해보면...


이렇게 버튼을 클릭 할때 마다 지정한 웹 페이지의 내용을 로그로 출력해 준다.