728x90

컴파일시 에러 메시지

Rejecting re-init on previously-failed class java.lang.Class<androidx.core.view.ViewCompat$2>


해결책

앱 build.gradle 에 추가

implementation 'androidx.core:core:1.5.0-alpha04'


https://developer.android.com/jetpack/androidx/releases/core?hl=ko 참조

블로그 이미지

Link2Me

,
728x90

Oracle에서 제공하는 JAVA 가 무료라서 많이 사용했는데, 기업 등에서는 라이선스 비용을 지불해야 하는 문제가 있어서 집에서 사용하는 것이기는 하지만 무료로 사용할 수 있는 Open JDK 를 설치해 보기로 했다.


Open JDK 파일 다운로드 : https://www.azul.com/downloads/

다운로드 받은 설치파일을 실행한다.





환경변수 등록

Open JDK 실행 경로 환경 변수 추가는 고급 시스템 설정을 통해 지정할 수 있다.

먼저 Windows 키 + Pause 키를 누른다.


JAVA_HOME 에 대한 변수 설정을 한다.




맨 위에 이미 등록되어 있는데, 9번과 같이 추가해본다. 그리고 맨 윗줄은 지웠다.


이제 설치 여부를 CMD 창에서 확인한다.

 "java -version" 명령을 실행하여 자바 실행 파일이 정상적으로 실행되는지 확인한다.


이제 Open JDK를 설치하는 모든 과정이 완료되었다.

블로그 이미지

Link2Me

,
728x90

Java 언어와 C/C++ 비교를 표로 정리했다.

언어

차이점 설명

C언어

절차지향 언어

Java 보다 처리 속도가 빠르다

소스를 컴파일하고 나면 플랫폼상에서 바로 실행될 수 있는 실행파일이 생성된다.

컴파일러가 중간 단계인 오브젝트 코드를 생성한 후, 링커가 필요한 라이브러리를 링크하여 최종 실행 가능한 실행파일을 생성한다.

오브젝트 코드 및 실행파일은 플랫폼에 따라 다르므로 플랫폼이 달라지면 컴파일 및 링크를 새로 해야한다. 

절차지향은 대부분 코드가 main메소드에 한방에 정의 됨

절차지향 언어는 클래스 구분없이 한줄 한줄 절차적으로 컴파일

하드웨어를 직접 제어할 수 있는 것이 C언어

포인터를 이용하여 메모리에 접근한다.

관련된 데이터를 하나로 모으기 위해 구조체(structure)나 공용체(union)을 사용한다.

C++

객체지향언어. 동적 메모리 직접 관리. C++의 메모리는 일반적으로 스마트 포인터가 관리한다.

소스를 컴파일하고 나면 플랫폼상에서 바로 실행될 수 있는 실행파일이 생성된다.

컴파일러가 중간 단계인 오브젝트 코드를 생성한 후, 링커가 필요한 라이브러리를 링크하여 최종 실행 가능한 실행파일을 생성한다.

오브젝트 코드 및 실행파일은 플랫폼에 따라 다르므로 플랫폼이 달라지면 컴파일 및 링크를 새로 해야한다. 

C++에서 new 키워드를 통해 객체를 생성하면 Heap영역에 객체가 할당되고, 일반적인 변수 선언 방식을 택하면 Stack영역에 할당이 된다.

Java 보다 처리 속도가 빠르다

객체지향은 여러 Class로 나누어 개발이 가능하다.

C++ 은 다중 상속이 가능하다. C++ 은 Interface는 지원하지 않는다.

값에 의한 매개변수 전달 또는 참조에 의한 매개변수 전달 

C++는 기본적으로 char 형이 1byte이며 부호 표시 변수 이기때문에 -129~127 까지 표현이 가능하고 ASCII 문자형을 표현한다.

C++은 이름 공간 레벨에 상수, 변수, 함수가 있을 수 있다.

C++에 있는 const는 개념적으로 '읽기 전용' 데이터임을 명시하며 자료형에 적용된다.

C++은 임의의 블록 크기로 메모리를 할당할 수 있다. 

C++에서는 초기화하지 않고 객체를 생성할 수 있다(쓰레기 값을 가지고 있다).

C++에는 메모리에 할당하였지만 접근 가능한 참조가 없는 객체가 있을 수 있다.

이런 접근 불가능한 객체는 소멸될 수도 없으므로 메모리 누수를 일으킨다.

C++은 C와 가장 하위 호환성이 좋은 언어이다.

C 라이브러리에 있는 운영 체제의 API 같은 것을 C++에서는 직접 사용할 수 있다.

C++은 보통 기계어로 직접 컴파일되고, 이를 운영 체제가 실행한다.

C++에서는 연산자오버로딩을 지원하지만, JAVA는 그렇지 않다.

Java

객체지향언어(Object Oriented Programming). 동적 메모리 가비지 컬렉션이 관리한다.

컴파일하면 JVM에서 실행가능한 바이트코드 형태의 클래스 파일이 생성되고, JVM을 통해 실행할 수 있다.

자바는 링크 과정이 없이 컴파일러가 바로 바이트 코드를 생성한다.

런타임에 필요한 클래스들이 JVM에 링크되며 클래스 로더가 동적으로 필요한 클래스를 로딩한다.

플랫폼 독립적이므로, 자바의 클래스 파일이 JVM에 실행되기 때문에 플랫폼이 달라져도 다시 컴파일하지 않아도 된다.

JAVA는 객체를 메모리의 Heap영역에만 할당할 수 있으나, C++의 경우 Heap과 Stack영역 모두에 할당이 가능하다.

자바는 c++ 와 달리 포인터를 지원하지 않고, 레퍼런스 만을 지원한다.

자바의 자료형은 크게 기본형과 레퍼런스 타입으로 분류할 수 있다.

레퍼런스 타입은 new 연산자를 통해서 heap 영역에 생성되는 자료형들을 의미한다. 레퍼런스 타입으로는 클래스, 배열, 인터페이스 가 있다. 

Java는 char형이 2byte 이고 unsigned 부호표시 불가이기 때문에 0~65535까지 표현이 가능하고 유니코드를사용한다.

C++ 는 float, double, long double을 지원하는데 Java 는 float, double을 지원한다. 

C++ 는 문자열을 위한 기본 자료형을 지원하지않는다. 대신 배열을 사용한다. 

Java는 문자열을 위한 기본형을 없지만 string이라는 클래스를 제공한다. 

JAVA는 익명클래스(Anonymous Class)를 지원하지만, C++의 경우 그렇지 않다.

JAVA는 동적바인딩(런타임 중 실행코드가 결정한다), C++은 정적바인딩(virtual 키워드로 동적바인딩 가능)

Java코드는 실행하면서 JVM인터프리터에의해 바이트코드를 기계코드로 변환한다.

객체지향은 여러 Class로 나누어 개발이 가능하다.

단일 상속을 지원하고, 하나의 Class가 여러개의 Interface를 Implement할 수 있다.

클래스와 메인을 왔다갔다하면서 읽어야 하기 때문에 해석하기가 악갼 복잡하다.

항상 값에 의한 매개변수 전달(call by value).  매개변수로 객체에 대한 참조값을 사용할 수는 있다.

자바에서는 모든 상수, 변수, 함수는 반드시 클래스나 인터페이스에 속해 있어야 한다.

자바에 있는 final은 변수가 다시 할당될 수 없음을 나타낸다. 

자바는 객체를 생성하는 방식으로만 메모리를 할당할 수 있다. 

자바는 기본 초기화가 강제로 수행된다(0 등으로 초기화 된다).

자바에서는 접근 불가능한 객체가 되기 전까지 그 객체는 가비지 콜렉터가 소멸시키지 않는다.

자바는 메모리가 아닌 다른 자원의 누수에 대해서는 C++에 비해 상대적으로 취약하다.

자바에서 운영 체제나 하드웨어 기능에 직접 접근하려면 자바 네이티브 인터페이스를 이용하여야만 한다.

자바는 보통 바이트코드로 컴파일되고, 자바 가상 머신이 인터프리터 방식으로 실행하거나

 JIT 컴파일러 방식으로 기계어로 컴파일한 다음 실행한다.


블로그 이미지

Link2Me

,
728x90

Android Material Design 기반 TabLayout 을 하단에 두고, ViewPager 를 상단에 두는 Layout 예제이다.




ViewPager : 좌우로 스와프하며 View를 전환하는 AdapterView


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ViewPagerActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/vp_layout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/tab_layout">

        <androidx.viewpager.widget.ViewPager
            android:id="@+id/viewPager"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        app:tabTextColor="@color/colorDarkGray"
        app:tabSelectedTextColor="@color/colorWhite"
        app:layout_collapseMode="pin"
        app:tabGravity="fill"
        app:tabBackground="@drawable/tab_color_selector"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/vp_layout">

        <com.google.android.material.tabs.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Home" />

        <com.google.android.material.tabs.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Map" />

        <com.google.android.material.tabs.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="공지" />

        <com.google.android.material.tabs.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="사랑" />

        <com.google.android.material.tabs.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="우정" />

    </com.google.android.material.tabs.TabLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
 


전체 코드는 https://github.com/jsk005/JavaProjects/tree/master/viewpager 에 올려두었다.

Fragment 기반 WebView 코드를 포함했고, 뒤로 가기 버튼 동작하도록 하는 걸 구글링해서 추가했다.

'안드로이드 > Layout' 카테고리의 다른 글

android 검색 + 버튼 배치  (0) 2021.03.14
하단 TabLayout + ViewPager2 Layout 예제  (0) 2020.12.30
LinearLayout 예제  (0) 2020.10.13
LinearLayout 동적 생성  (0) 2020.10.01
SMS authentication with OTP Layout Example  (0) 2020.09.14
블로그 이미지

Link2Me

,
728x90

자바와 코틀린의 기능을 표로 간략하게 비교하는 걸로 자주 사용하는 걸 추가해 두고자 한다.


기능

언어

설명

Intent

Java

  Intent intent = new Intent(getApplicationContext(), SubActivity.class);
  intent.putExtra("name","홍길동");
  startActivity(intent);

  Intent intent = getIntent(); /*데이터 수신*/
  String name = intent.getExtras().getString("name");
  int age = intent.getExtras().getInt("age");

kotlin

  val intent = Intent(this, secondActivity::class.java)
  intent.putExtra("phone",""010-0000-0000")
  startActivity(intent)

  val intent = getIntent()
  val phone =intent.getStringExtra("phone").toString()


기능

언어

설명

Context

Java

 Context mContext;
 mContext= LoginActivity.this;

kotlin

 lateinit var mContext: Context
 mContext = this@LoginActivity


기능

언어

설명

TAG

Java

 private final String TAG = this.getClass().getSimpleName();

kotlin

 private val TAG = this.javaClass.simpleName


블로그 이미지

Link2Me

,
728x90

Java static 메서드가 포함된 Class를 코틀린에서 직접 호출하니까 에러가 발생하고 제대로 동작이 안된다.


그래서 Android Studio 에서 기본 제공하는 방법으로 파일을 변환하면 Object 파일로 변환을 한다.

이렇게 변환한 파일을 다시 수작업으로 companion object 안에 메서드를 감싸는 방법으로 해주는 것이 좋더라.


자바 코드

public class Utils extends AppCompatActivity {

    public static void showAlert(Context context, String title, String message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(title);
        builder.setMessage(message)
                .setCancelable(false)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

    public static OkHttpClient createOkHttpClient() {
        // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(interceptor);
        return builder.build();
    }

    public static boolean isValidmobileNO(String cellphoneNumber) {
        boolean returnValue = false;
        Log.i("cell", cellphoneNumber);
        String regex = "^\\s*(010|011|012|013|014|015|016|017|018|019)(-|\\)|\\s)*(\\d{3,4})(-|\\s)*(\\d{4})\\s*$";
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(cellphoneNumber);
        if (m.matches()) {
            returnValue = true;
        }
        return returnValue;
    }

    public static int getVersionCode(Context context) {
        PackageInfo packageInfo = null;
        try {
            packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        int versionCode = packageInfo.versionCode;
        return versionCode;
    }

}
 



코틀린 변환 코드

class Utils : AppCompatActivity() {

    companion object {
        fun showAlert(context: Context, title: String?, message: String?) {
            val builder = AlertDialog.Builder(context)
            builder.setTitle(title)
            builder.setMessage(message)
                .setCancelable(false)
                .setPositiveButton("OK") { dialog, id -> dialog.dismiss() }
            val alert = builder.create()
            alert.show()
        }

        fun createOkHttpClient(): OkHttpClient {
            // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
            val builder = OkHttpClient.Builder()
            val interceptor = HttpLoggingInterceptor()
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
            builder.addInterceptor(interceptor)
            return builder.build()
        }

        fun isValidmobileNO(cellphoneNumber: String?): Boolean {
            var returnValue = false
            Log.i("cell", cellphoneNumber!!)
            val regex =
                "^\\s*(010|011|012|013|014|015|016|017|018|019)(-|\\)|\\s)*(\\d{3,4})(-|\\s)*(\\d{4})\\s*$"
            val p = Pattern.compile(regex)
            val m = p.matcher(cellphoneNumber)
            if (m.matches()) {
                returnValue = true
            }
            return returnValue
        }

        fun getVersionCode(context: Context): Int {
            var packageInfo: PackageInfo? = null
            try {
                packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
            } catch (e: PackageManager.NameNotFoundException) {
                e.printStackTrace()
            }
            val versionCode = packageInfo!!.versionCode
            return versionCode
        }

}


안드로이드 스튜디오가 기본 제공하는 변환을 이용하면, 약간 코틀린 문법에 맞게 변수를 조정해준다.

이렇게 하고 나서 코틀린에서 해당 메서드를 호출하면 정상으로 잘 동작한다.


블로그 이미지

Link2Me

,
728x90

LineaLayout 지식인 질의 예제


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="24dp"
    android:weightSum="6.5">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:orientation="horizontal"
        android:layout_weight="2">

        <View
            android:id="@+id/view1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#FF0000"
            android:layout_weight="1" />

        <View
            android:id="@+id/view2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#0000FF"
            android:layout_weight="1" />

        <View
            android:id="@+id/view3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#00FF00"
            android:layout_weight="1" />
    </LinearLayout>

    <View
        android:id="@+id/view4"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:background="#FFFF00"
        android:layout_weight="1"/>

    <View
        android:id="@+id/view5"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:background="#0000FF"
        android:layout_weight="1"/>

    <View
        android:id="@+id/view6"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:background="#00FF00"
        android:layout_weight="1"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:orientation="vertical"
        android:layout_marginTop="20dp"
        android:layout_weight="1">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <Button
                android:id="@+id/button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="Button"
                android:textAllCaps="false"/>

            <ImageButton
                android:id="@+id/imageButton"
                android:layout_width="wrap_content"
                android:layout_height="50dp"
                android:layout_gravity="center"
                android:src="@drawable/ic_baseline_videocam_24" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="5dp">

            <TextView
                android:id="@+id/textView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="20192019"
                android:textSize="32sp"
                android:textColor="#FF0000"/>

            <TextView
                android:id="@+id/textView2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="24dp"
                android:text="홍길동"
                android:textSize="32sp"
                android:textColor="#0000FF"/>
        </LinearLayout>
    </LinearLayout>
</LinearLayout>
 



Vector Asset 추가하는 방법으로 이미지를 추가하면 된다.

'안드로이드 > Layout' 카테고리의 다른 글

하단 TabLayout + ViewPager2 Layout 예제  (0) 2020.12.30
하단 TabLayout + ViewPager Layout 예제  (0) 2020.11.01
LinearLayout 동적 생성  (0) 2020.10.01
SMS authentication with OTP Layout Example  (0) 2020.09.14
EditText DatePicker  (0) 2020.08.18
블로그 이미지

Link2Me

,
728x90

안드로이드 View Binding 기능을 어떤 걸로 테스트하면 좋을까 생각하다가 GitHub 에서 계산기를 받아서 그걸로 View Binding 테스트를 했다.


뷰 바인딩은 view와 상호작용하는 코드를 더욱 쉽게 작성할 수 있도록 도와주는 기능이다.


View Binding 을 하는 방법

1. 앱 build.gradle 추가사항

    viewBinding {
        enabled = true
    } 


2. Java code 수정사항

setContentView(R.layout.activity_main); 대신에

private ActivityMainBinding binding;

binding = ActivityMainBinding.inflate(getLayoutInflater()); // 1. inflate 호출하여 인스턴스 생성

View view = binding.getRoot(); // 2. getRoot()

setContentView(view); // 3. 화면상의 활성 뷰로 만든다.


binding.btnClear.setOnClickListener(v -> {
    binding.tvInput.setText("");
    binding.tvOutput.setText("");
});


3. XML 파일 수정사항은 없다.

    - XML 파일을 분리하여 호출하는 구조로는 binding 을 인식하지 못하더라. 그래서 1개의 파일로 작성했음.

    - findViewById 호출 대신에 직접 id값을 binding 한다.


<Button
    android:id="@+id/btnClear"
    android:layout_width="wrap_content"
    android:layout_height="100dp"
    android:layout_row="0"
    android:layout_column="0"
    android:backgroundTint="@color/colorWhiteGray"
    android:text="CL"
    android:textColor="@color/colorBlack"
    android:textSize="25sp" />


계산기 예제코드

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    viewBinding {
        enabled = true
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
    implementation 'com.google.android.material:material:1.2.1'
    // Rhino is an open-source implementation of JavaScript written entirely in Java.
    implementation group: 'org.mozilla', name: 'rhino', version: '1.7.13'

}


import android.os.Bundle;
import android.view.View;

import androidx.appcompat.app.AppCompatActivity;

import com.link2me.android.calculator.databinding.ActivityMainBinding;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;

public class MainActivity extends AppCompatActivity {
    String process;
    Boolean checkBracket = false;

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater()); // 1. inflate 호출하여 인스턴스 생성
        View view = binding.getRoot(); // 2. getRoot()
        setContentView(view); // 3. 화면상의 활성 뷰로 만든다.

        binding.btnClear.setOnClickListener(v -> {
            binding.tvInput.setText("");
            binding.tvOutput.setText("");
        });

        binding.btn0.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "0");
        });

        binding.btn1.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "1");
        });

        binding.btn2.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "2");
        });

        binding.btn3.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "3");
        });

        binding.btn4.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "4");
        });

        binding.btn5.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "5");
        });

        binding.btn6.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "6");
        });

        binding.btn7.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "7");
        });

        binding.btn8.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "8");
        });

        binding.btn9.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "9");
        });

        binding.btn0.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "0");
        });

        binding.btnAdd.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "+");
        });

        binding.btnMinus.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "-");
        });

        binding.btnMultiply.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "×");
        });

        binding.btnDivision.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "÷");
        });

        binding.btnDot.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + ".");
        });

        binding.btnPercent.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "%");
        });

        binding.btnEquals.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();

            process = process.replaceAll("×", "*");
            process = process.replaceAll("%",  "/100");
            process = process.replaceAll("÷","/");

            Context rhino = Context.enter();
            rhino.setOptimizationLevel(-1);

            String finalResult = "";

            try {
                Scriptable scriptable = rhino.initStandardObjects();
                finalResult = rhino.evaluateString(scriptable,process,"javascript",1,null).toString();
            }catch (Exception e){
                finalResult="0";
            }
            binding.tvOutput.setText(finalResult);
        });

        binding.btnBracket.setOnClickListener(v -> {
            if (checkBracket){
                process = binding.tvInput.getText().toString();
                binding.tvInput.setText(process + ")");
                checkBracket = false;
            }
            else{
                process = binding.tvInput.getText().toString();
                binding.tvInput.setText(process + "(");
                checkBracket = true;
            }
        });

    }
}


전체 코드는 https://github.com/jsk005/JavaProjects/tree/master/calculator 에 있다.

블로그 이미지

Link2Me

,
728x90

Android 코드에서 실제 파일 존재 유무를 확인하는 방법으로 했더니 Main Thread UI에서 작업을 한다고 경고 메시지가 나온다.


    private class PhotoURLExists extends AsyncTask<String, Void, Boolean> {
        @Override
        protected Boolean doInBackground(String... params) {
            try {
                HttpURLConnection.setFollowRedirects(false);
                HttpURLConnection con =  (HttpURLConnection) new URL(params[0]).openConnection();
                con.setRequestMethod("HEAD");
                return (con.getResponseCode() == HttpURLConnection.HTTP_OK);
            }
            catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    } 


위 코드로 확인을 할 수는 있으나, 제대로 처리하는 것이 아닌거 같아서 PHP에서 자료를 가져올 때 아예 체크하는 로직으로 대체하였다.


테이블 칼럼에는 파일명이 존재하는데, 실제로는 파일이 존재하지 않을 수가 있다.

이럴 경우 Android 코드에서 파일이 존재하는 줄 알고 가져오기를 하면 에러가 발생한다.

이걸 방지하는 방법은 PHP 코드에서 파일이 폴더에 존재하는지 검사하여 없으면 공백을 반환하도록 하는 것이 좋다.


<?php
if(!isset($_SESSION)) {
    session_start();
}
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // POST 전송으로 전달받은 값 처리
    if(!(isset($idx) && !empty($idx))) {
        echo 0;
        exit;
    }
    require_once 'phpclass/dbconnect.php';
    require_once 'phpclass/loginClass.php';
    $c = new LoginClass();
    $sql = "select idx,userNM,mobileNO,telNO,photo from Person";
    if(!empty($search)) {
        $where = "userNM LIKE '%".$search."%' or mobileNO LIKE '%".$search."%'";
    } else {
        $where = "";
    }

    if(strlen($where) > 0){
        $sql .= " where ".$where;
    }

    $R = array(); // 결과 담을 변수 생성
    $result = $c->putDbArray($sql);
    while($row = $result->fetch_assoc()) {
        if($row['photo'] == NULL) {
            $row['photo'] = "";
        } else {
            $path = "./photos/".$row['photo'];
            if(!file_exists($path)) {
                $row['photo'] = "";
            }
        }
        array_push($R, $row);
    }
    echo json_encode(array('result'=>$R)); //배열-문자열등을 json형식의 '문자열'로 변환
}
?>

<?php
class DBController {
    protected $db; // 변수를 선언한 클래스와 상속받은 클래스에서 참조할 수 있다.

    // 생성자
    function __construct() {
        $this->db = $this->connectDB();
        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.
    }

    // 소멸자(destructor)
    function __destruct() {
        mysqli_close($this->connectDB());
    }

    private function connectDB() {
        require_once 'dbinfo.php';
        // MySQLi 객체지향 DB 연결
        $conn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE);
        return $conn; // return database handler
    }
}
?>

<?php
class LoginClass extends DBController {
    // class 자식클래스 extends 부모클래스
    // override : 부모 클래스와 자식 클래스가 같은 메소드를 정의했을 경우 자식 클래스가 우선시된다.

    // 여기에 함수를 작성하면 된다.

    public function putDbArray($sql) {
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        $result = $stmt->get_result();
        return $result;
    }

    public function SearchFiltering($str){
        // 해킹 공격을 대비하기 위한 코드
        $str = preg_replace("/[\s\t\'\;\"\=\-]+/","", $str); // 공백이나 탭 제거, 특수문자 제거
        return $str;
    }

}


MySQLi 객체지향 방식으로 Class를 작성해보니, 함수화하기가 쉽지 않다.
변수를 받아서 처리하는 경우가 쉽지 않다.
PDO 방식으로 하는 것은 변수처리까지 원활하게 할 수 있다.


블로그 이미지

Link2Me

,
728x90

LinearLayout 동적 생성 코드이다.


import android.graphics.Color;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class TestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        LinearLayout myLinearLayout = new LinearLayout(this);
        myLinearLayout.setOrientation(LinearLayout.VERTICAL);

        LinearLayout.LayoutParams mParams = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);

        final EditText editText = new EditText(this);
        editText.setHint("여기에 입력하세요");
        mParams.setMargins(0,50,0,10);
        editText.setLayoutParams(mParams);
        myLinearLayout.addView(editText);

        DisplayMetrics dm = getResources().getDisplayMetrics();
        int size = Math.round(20 * dm.density);

        Button btn = new Button(this);
        btn.setText("버튼");
        btn.setBackgroundColor(Color.YELLOW);
        mParams.topMargin = size;
        btn.setLayoutParams(mParams);
        myLinearLayout.addView(btn);

        LinearLayout.LayoutParams tv_params = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);

        final TextView textView = new TextView(this);
        textView.setText("텍스트뷰입니다.");
        textView.setTextColor(Color.MAGENTA);
        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP,18);
        tv_params.gravity = Gravity.CENTER;
        tv_params.setMargins(0,50,0,0);
        textView.setLayoutParams(tv_params);
        myLinearLayout.addView(textView);

        setContentView(myLinearLayout);

        btn.setOnClickListener(view -> textView.setText(editText.getText().toString()));
    }

}


결과 화면


 


코드파일 및 결과 이미지

dynamicLayout.zip


'안드로이드 > Layout' 카테고리의 다른 글

하단 TabLayout + ViewPager Layout 예제  (0) 2020.11.01
LinearLayout 예제  (0) 2020.10.13
SMS authentication with OTP Layout Example  (0) 2020.09.14
EditText DatePicker  (0) 2020.08.18
Floating Action Button  (0) 2020.07.11
블로그 이미지

Link2Me

,
728x90

Android Text 를 Speech 로 변환하는 예제다.


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".TextToSpeechActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/layout_appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </com.google.android.material.appbar.AppBarLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/layout_textvoice"
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginTop="30dp"
        android:layout_marginEnd="24dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/layout_appbar">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/et_text2voice"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="텍스트를 입력하세요"
            android:padding="10dp"></com.google.android.material.textfield.TextInputEditText>

    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.button.MaterialButton
        android:id="@+id/btn_speech"
        style="@style/Widget.AppCompat.Button.Borderless.Colored"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginTop="30dp"
        android:layout_marginEnd="24dp"
        android:padding="10dp"
        android:text="음성변환"
        android:textColor="@color/colorOrangeDark"
        android:textSize="16sp"
        android:textStyle="bold"
        app:backgroundTint="@color/colorSkyBlue"
        app:cornerRadius="15dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/layout_textvoice" />

</androidx.constraintlayout.widget.ConstraintLayout>
 



import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import java.util.Locale;

public class TextToSpeechActivity extends AppCompatActivity {
    private final String TAG = this.getClass().getSimpleName();
    Context mContext;

    private TextToSpeech textToSpeech;
    private EditText speakText;
    private Button speakBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_speech);
        mContext = TextToSpeechActivity.this;

        initView();
    }

    private void initView() {
        Toolbar toolbar = findViewById(R.id.toolbar);
        toolbar.setTitle("TextToSpeech");
        setSupportActionBar(toolbar);

        speakText = findViewById(R.id.et_text2voice);
        speakBtn = findViewById(R.id.btn_speech);

        textToSpeech = new TextToSpeech(getApplicationContext(), status -> {
            if (status != TextToSpeech.ERROR) {
                textToSpeech.setLanguage(Locale.KOREAN);
            }
        });

        speakBtn.setOnClickListener(v -> texttoSpeak());
    }

    private void texttoSpeak() {
        String text = speakText.getText().toString();
        if ("".equals(text)) {
            text = "Please enter some text to speak.";
            Toast.makeText(getApplicationContext(), text, Toast.LENGTH_SHORT).show();
            return;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, null);
        } else {
            String utteranceId = this.hashCode() + "";
            textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, utteranceId);
        }
    }

    @Override
    protected void onDestroy() {
        if (textToSpeech != null) {
            textToSpeech.stop();
            textToSpeech.shutdown();
        }
        super.onDestroy();
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        startActivity(new Intent(mContext,SplashActivity.class));
        finish();
    }
}


예제 전체 소스코드는 https://github.com/jsk005/JavaProjects/tree/master/speechtext 에 있다.

블로그 이미지

Link2Me

,
728x90

Retrofit2 라이브러리를 이용하여 파일을 업로드하는 예제 예시다.

파일 업로드 외에 POST 변수 idx를 추가해서 보낼 때,

RequestBody descBody = RequestBody.create(MediaType.parse("text/plain"), idx);

가 추가하면 된다.


Retrofit2 통신에 대한 기본 이해는 https://link2me.tistory.com/1806 에 이미지 도식도를 참조하시라.



앱 build.gradle

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
    implementation 'com.google.android.material:material:1.2.1'

    implementation 'com.squareup.okhttp3:okhttp:4.4.0'
    implementation 'com.squareup.retrofit2:retrofit:2.7.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.7.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'
} 


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


public interface DataCommAPI {

    @Multipart
    @POST(RetrofitURL.URL_ImageUpload)
    Call<UploadResult> uploadFile(@Part("idx") RequestBody idx,
                                  @Part MultipartBody.Part uploaded_file);
}

public class UploadResult {
    private String result;

    public String getResult() {
        return result;
    }

}

public class RetrofitURL {
    public static final String IPADDRESS = "http://www.abc.com/androidSample/upload/";

    public static final String URL_ImageUpload = "upload.php";
}

public class APIRequest {
    static Retrofit retrofit = null;

    public static Retrofit getClient() {
        if (retrofit==null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(RetrofitURL.IPADDRESS)
                    .client(createOkHttpClient())
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }

    public static OkHttpClient createOkHttpClient() {
        // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(interceptor);
        return builder.build();
    }
}

private void uploadImage(String sourceImageFile, String idx){
     DataCommAPI retrofitInterface = APIRequest.getClient().create(DataCommAPI.class);

     File file = new File(sourceImageFile);
     RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
     MultipartBody.Part body = MultipartBody.Part.createFormData("uploaded_file", file.getName(), requestFile);
     RequestBody descBody = RequestBody.create(MediaType.parse("text/plain"), idx);

     Call<UploadResult> call = retrofitInterface.uploadFile(descBody,body);
     call.enqueue(new Callback<UploadResult>() {
         @Override
         public void onResponse(Call<UploadResult> call, Response<UploadResult> response) {
             if (response.isSuccessful()) {
                 UploadResult responseBody = response.body();
                 if(responseBody.getResult().contains("success")){
                     Toast.makeText(getApplicationContext(), "파일 업로드 성공", Toast.LENGTH_LONG).show();
                 } else {
                     Utils.showAlert(mContext,"파일 업로드","파일 업로드 실패");
                 }

             } else {
                 ResponseBody errorBody = response.errorBody();
                 Gson gson = new Gson();
                 try {
                     Response errorResponse = gson.fromJson(errorBody.string(), Response.class);
                    
Toast.makeText(getApplicationContext(), errorResponse.message(),

                        Toast.LENGTH_LONG).show();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
         }

         @Override
         public void onFailure(Call<UploadResult> call, Throwable t) {
             Log.e(TAG, "onFailure: "+t.getLocalizedMessage());
         }
     });

 }




블로그 이미지

Link2Me

,
728x90

Cutstom Dialog 기능을 이용하여 SMS authentication with One Time Password 처리를 위한 Layout 구성 예제이다.


custom_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:id="@+id/parent_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@android:color/transparent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.cardview.widget.CardView
        android:id="@+id/parent_card_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/transparent">

        <RelativeLayout
            android:id="@+id/inner_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/transparent">

            <com.google.android.material.textview.MaterialTextView
                android:id="@+id/dialog_title_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/default_title_text"
                android:textSize="20sp"
                android:textStyle="bold"
                android:textColor="@android:color/black"
                android:textAlignment="center"
                android:layout_centerHorizontal="true"
                android:layout_marginTop="10dp"
                android:paddingLeft="10dp"
                android:paddingRight="10dp"
                android:paddingTop="5dp"
                android:paddingBottom="2dp"/>

            <com.google.android.material.textview.MaterialTextView
                android:id="@+id/dialog_explain_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/dialog_title_text"
                android:text="@string/default_explain_text"
                android:textSize="14sp"
                android:textColor="@android:color/black"
                android:textAlignment="center"
                android:layout_centerHorizontal="true"
                android:layout_marginTop="10dp"
                android:paddingLeft="10dp"
                android:paddingRight="10dp"
                android:paddingTop="2dp"
                android:paddingBottom="5dp"/>

            <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/et_auth_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@id/dialog_explain_text"
                android:layout_marginTop="10dp"
                android:layout_marginLeft="20dp"
                android:layout_marginRight="20dp" >

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/et_auth_text"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@color/colorWhite"
                    android:textSize="16sp"
                    android:hint="@string/prompt_auto"
                    android:inputType="numberDecimal"
                    android:imeOptions="actionDone"
                    android:selectAllOnFocus="true">

                </com.google.android.material.textfield.TextInputEditText>

            </com.google.android.material.textfield.TextInputLayout>

            <LinearLayout
                android:id="@+id/btn_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="20dp"
                android:layout_below="@id/et_auth_layout"
                android:gravity="top"
                android:orientation="horizontal">

                <com.google.android.material.button.MaterialButton
                    android:id="@+id/btn_disagree"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginRight="10dp"
                    android:layout_weight="1"
                    android:backgroundTint="@color/colorOrangeDark"
                    app:cornerRadius="10dp"
                    android:text="취소" />

                <com.google.android.material.button.MaterialButton
                    android:id="@+id/btn_agree"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="10dp"
                    android:layout_weight="1"
                    android:backgroundTint="@color/colorBlue"
                    app:cornerRadius="10dp"
                    android:text="전송" />

            </LinearLayout>
        </RelativeLayout>
    </androidx.cardview.widget.CardView>
</RelativeLayout>
 


예제의 코드는 https://github.com/jsk005/JavaProjects/tree/master/Interfaces/src/main/java/com/link2me/android/interfaces 에 올려져 있다.

Custom Dialog 를 Interface로 처리하는 코드 예제일 뿐이지 SMS OTP 인증 전체 코드는 아니다.


'안드로이드 > Layout' 카테고리의 다른 글

LinearLayout 예제  (0) 2020.10.13
LinearLayout 동적 생성  (0) 2020.10.01
EditText DatePicker  (0) 2020.08.18
Floating Action Button  (0) 2020.07.11
CardView Layout 예제  (0) 2020.04.12
블로그 이미지

Link2Me

,
728x90

맨날 Debugging을 Log 찍어가면서 해왔는데, 우연히 검색해보다 페이스북에서 내놓은 Stetho 라이브러리를 이용하면 편하게 디버깅할 수 있다는 걸 알아서 사용해보고 간략하게 적어둔다.

 

https://github.com/facebook/stetho 에 가면 최신버전을 알 수 있다.

 

앱 build.gradle 추가

 

 

AndroidManifest.xml 추가

를 추가한다.

 

 

MyApplication Class 추가

public class MyApplication extends Application {
  public void onCreate() {
    super.onCreate();
    Stetho.initializeWithDefaults(this);
  }
} 

 

 

여기까지 하고 나서 앱을 컴파일 하면 연결된 폰의 정보를 크롬브라우저에서 확인할 수 있다.

 

크롬 주소창에 chrome://inspect 라고 입력한다.

아래와 같이 연결된 삼성폰 정보가 보이고 앱 명칭이 보인다.

 

 

2번을 누르면 새로운 창이 뜬다.

 

SharedPreference 정보를 살펴보니 저장된 정보가 보인다.

테스트 목적으로 만드는 템플릿 형식의 앱 개발이라 정보는 보여줘도 무방하기에 그대로 보이도록 했다.

PIN 인증번호 6자리 입력한 정보가 그대로 보인다.

 

여기서 직접 정보를 수정할 수도 있다.

 

PIN 번호도 임의변경하고 기존 PIN번호로 인증 시도했더니 안된다. 변경 PIN 번호 입력해야 가능하다.

이름과 서버에서 가져오는 이미지 저장 idx 값을 임의로 1에서 2로 변경해보고 이름도 홍길동으로 변경해봤다.

 

 

SQLite도 adb shell로 들어가서 확인하거나, db 파일 추출해서 pc에서 확인할 필요없이,
SQL을 직접 날려가며 확인할 수 있다고 하는데 SQLite DB 코드 만들면 테스트 해봐야겠다.

 

Network 연결방식은 Volley 라이브러리를 사용해서 그런지 정보가 안보인다.

Retrofit2 라이브러리를 사용한 코드로 테스트 해보면 제대로 보이려나?

'안드로이드 > Android Studio' 카테고리의 다른 글

ADB(Android Debug Bridge)  (0) 2021.01.07
OpenJDK 설치  (0) 2020.11.08
android SDK 설치 위치 변경  (0) 2020.07.06
[코틀린] Anko 라이브러리 추가 방법  (0) 2020.04.20
자바와 코틀린 함께 사용하기  (0) 2020.03.22
블로그 이미지

Link2Me

,
728x90

Android 에서 로그인을 하면 서버에서 PHP가 받아서 처리하는 로그인 예제이다.

결과를 json 으로 받아서 Android에서 저장해서 처리한다.


<?php
// 파일을 직접 실행하는 비정상적 동작을 방지 하기 위한 목적
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    if(isset($userID) && !empty($userID) && isset($password) && !empty($password)) {
        require_once 'phpclass/dbconnect.php';
        require_once 'phpclass/loginClass.php';
        $c = new LoginClass();

        header("Cache-Control: no-cache, must-revalidate");
        header("Content-type: application/json; charset=UTF-8");

        $rs = $c->LoginUserChk($userID,$password,$uID);
        if($rs > 0){
            $user = $c->getUser($userID, $password);
            if ($user != false) {
                $_SESSION['userID'] = $user['userID'];
                $_SESSION['userNM'] = $user['userNM'];
                $_SESSION['admin'] = $user['admin'];

                $row = array("userNM"=>$user['userNM'],"mobileNO"=>$user['mobileNO'],"profileImg"=>$user['idx']);

                $status = "success";
                $message = "";
                $userinfo = $row;
            } else {
                $status = "로그인 에러";
                $message = "다시 한번 시도하시기 바랍니다.";
                $userinfo = null;
            }

        } else if($rs === -1){
            $status = "단말 불일치";
            $message = '등록 단말 정보가 일치하지 않습니다. 관리자에게 문의하시기 바랍니다.';
            $userinfo = null;
        } else {
            $status = "로그인 에러";
            $message = '로그인 정보가 일치하지 않습니다';
            $userinfo = null;
        }
        $result = array(
            'status' => $status,
            'message' => $message,
            'userinfo' => $userinfo
        );
        echo json_encode($result);
    }
} else { // 비정상적인 접속인 경우
    echo 0; // loginChk.php 파일을 직접 실행할 경우에는 화면에 0을 찍어준다.
    exit;
}
?>


본 코드의 안드로이드 처리 부분 (Volley 라이브러리 활용)

https://github.com/jsk005/JavaProjects/blob/master/volleyloginsample/src/main/java/com/link2me/android/loginsample/LoginActivity.java 부분을 살펴보면 이해가 될 것이다.


Retrofit2 활용 예제는 GitHub에는 올리지 않았다.

@Parcelize
data class LoginResult (
    val status: String = "",
    val message: String = "",
    val userinfo: UserInfo? = null
): Parcelable

@Parcelize
data class UserInfo (
        val userNM: String = "",
        val mobileNO: String = "",
        val profileImg: String = ""
): Parcelable

void AutoLoginProgress() {
    userID = PrefsHelper.read("userid", "");
    userPW = PrefsHelper.read("userpw", "");

    if (userID != null && !userID.isEmpty() && userPW != null && !userPW.isEmpty()) {
        String uID = Utils.getDeviceId(mContext); // 스마트폰 고유장치번호
        String mfoneNO = Utils.getPhoneNumber(mContext); // 스마트폰 전화번호
        String AppVersion = String.valueOf(Utils.getVersionCode(mContext));

        mloginService.Login(userID,userPW,uID,mfoneNO,AppVersion)
                .enqueue(new Callback<LoginResult>() {
                    @Override
                    public void onResponse(Call<LoginResult> call, retrofit2.Response<LoginResult> response) {
                        LoginResult result = response.body();
                        if(result.getStatus().contains("success")){
                            PrefsHelper.write("userNM",result.getUserinfo().getUserNM());
                            PrefsHelper.write("mobileNO",result.getUserinfo().getMobileNO());
                            PrefsHelper.write("profileImg",result.getUserinfo().getProfileImg());
                        } else {
                            if(result.getStatus().contains("로그인 에러")){
                                startActivity(new Intent(SplashActivity.this, LoginActivity.class));
                                finish();
                            } else {
                                Utils.showAlert(mContext, result.getStatus(), result.getMessage());
                            }
                        }
                    }

                    @Override
                    public void onFailure(Call<LoginResult> call, Throwable t) {

                    }
                });
    } else {
        startActivity(new Intent(getApplication(), LoginActivity.class));
        finish();
    }
}


블로그 이미지

Link2Me

,
728x90

Intent 를 이용한 메일 발송 함수 이다.

구글 메일에서도 잘 보내지고, 기본 메일에서도 잘 보내진다.


테스트 환경 : 삼성 갤럭스 노트9


private void sendEmail_default(String emailTo, String subject, String message){
    Intent emailSelectorIntent = new Intent( Intent.ACTION_SENDTO );
    emailSelectorIntent.setData( Uri.parse( "mailto:" ) );

    final Intent emailIntent = new Intent( Intent.ACTION_SEND );
    emailIntent.putExtra( Intent.EXTRA_EMAIL, new String[]{ emailTo } );
    emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
    emailIntent.putExtra(Intent.EXTRA_TEXT, message);
    emailIntent.addFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION );
    emailIntent.addFlags( Intent.FLAG_GRANT_WRITE_URI_PERMISSION );
    emailIntent.setSelector( emailSelectorIntent );

    startActivity( emailIntent );


}


블로그 이미지

Link2Me

,
728x90

회원 로그인 후 회원 정보를 가져와서 ProfileView 함수에 처리하는 로직이다.


private void ProfileView() {
    ImageView img_profile = findViewById(R.id.img_profile);
    TextView userNM = findViewById(R.id.tv_userNM);
    TextView mobileNO = findViewById(R.id.tv_mobileNO);

    String photoURL = Value.PhotoADDRESS + PrefsHelper.read("profileImg","") + ".jpg";
    // 사진 이미지가 존재하지 않을 수도 있으므로 존재 여부를 체크하여 존재하면 ImageView 에 표시한다.
    PhotoURLExists task = new PhotoURLExists();
    try {
        if(task.execute(photoURL).get()==true){
            Glide.with(mContext).load(photoURL).override(170, 200).into(img_profile);
        }
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    userNM.setText(PrefsHelper.read("userNM",""));
    mobileNO.setText(PrefsHelper.read("mobileNO",""));
}

private class PhotoURLExists extends AsyncTask<String, Void, Boolean> {
    @Override
    protected Boolean doInBackground(String... params) {
        try {
            HttpURLConnection.setFollowRedirects(false);
            HttpURLConnection con =  (HttpURLConnection) new URL(params[0]).openConnection();
            con.setRequestMethod("HEAD");
            return (con.getResponseCode() == HttpURLConnection.HTTP_OK);
        }
        catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
} 


앱 build.gradle 추가

implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' 


Glide.with(this)
    .load("이미지 url...")
    .override(이미지 사이즈) // ex) override(170, 200)
    .into(imageView);


Glide 최신버전 확인 : https://github.com/bumptech/glide


참고하면 도움되는 자료

AsyncTask return 결과 처리하는 방법 : https://link2me.tistory.com/1516

[Java] SharedPreferences Singleton

출처: https://link2me.tistory.com/1827 [소소한 일상 및 업무TIP 다루기]
[Java] SharedPreferences Singleton

출처: https://link2me.tistory.com/1827 [소소한 일상 및 업무TIP 다루기]
[Java] SharedPreferences Singleton

출처: https://link2me.tistory.com/1827 [소소한 일상 및 업무TIP 다루기]

[Java] SharedPreferences Singletonhttps://link2me.tistory.com/1827


블로그 이미지

Link2Me

,
728x90

zxing 을 이용하여 QR코드를 스캔하는 방법에 대한 코드 작성에 필요한 핵심 내용을 기술한다.


앱 build.gradle

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'com.journeyapps:zxing-android-embedded:3.6.0'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.google.android.material:material:1.1.0'
}


AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.link2me.android.qrcodesample">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission
        android:name="android.permission.REQUEST_INSTALL_PACKAGES"
        tools:ignore="ProtectedPermissions" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="false"
        android:hardwareAccelerated="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:usesCleartextTraffic="true">
        <activity android:name=".MainActivity"
            android:screenOrientation="portrait"/>
        <activity
            android:name="com.journeyapps.barcodescanner.CaptureActivity"
            android:screenOrientation="portrait"
            tools:replace="screenOrientation" />
        <activity android:name=".SplahActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".ScanQRActivity"
            android:screenOrientation="portrait"/>
    </application>

</manifest>


ScanQRActivity.java

public class ScanQRActivity extends AppCompatActivity {
    private final String TAG = this.getClass().getSimpleName();
    private IntentIntegrator qrScan;
    Context mContext;
    String QRcode;
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scanqr);
        mContext = ScanQRActivity.this;

        qrScan = new IntentIntegrator(this);
        qrScan.setOrientationLocked(false);
        qrScan.setPrompt("QRcode Sample!");
        qrScan.initiateScan();

        textView = findViewById(R.id.textView);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if(result != null) {
            if(result.getContents() == null) {
                Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show();
                Intent intent = new Intent(ScanQRActivity.this, MainActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                startActivity(intent);
                finish();
            } else {
                if(result.getContents() != null) {
                    QRcode = result.getContents();
                    PrefsHelper.write("QRCode",result.getContents());
                    // QRcode 결과를 가지고 서버에서 정보를 조회하여 가져오거나, 다른 처리를 하면 된다.
                    // QRcode 읽은 결과를 화면에 표시
                    textView.setText(QRcode);
                }
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

}


resource 등 코드 세부적인 사항은 https://github.com/jsk005/JavaProjects/tree/master/qrcodesample 를 참조하시길.

'안드로이드 > Android QRCode' 카테고리의 다른 글

PHP TCPDF 설치 및 샘플 코드  (0) 2020.05.30
PHP QR code 생성하는 방법  (0) 2020.03.04
QRCode 개요  (0) 2020.03.03
블로그 이미지

Link2Me

,
728x90

class OuterClass{
  //중첩클래스
  class NestedClass
}

중첩클래스의 class 키워드 앞에 inner 키워드를 붙이면 내부클래스가 된다.
class OuterClass{
  //내부클래스
  inner class InnerClass
}

이 두 클래스에 대해서 공통점과 차이점을 간단히 요약해보면 다음과 같다.
공통점 : 클래스 내부에 다른 클래스로 정의된다.
외형적 차이점 : inner 키워드를 쓰면 내부클래스, 안쓰면 중첩클래스이다.
기능적 차이점 : 중첩클래스는 OuterClass의 참조를 가지지 않지만 내부클래스는 OuterClass의 인스턴스를 참조를 가진다.
중첩클래스는 외형적으로는 OuterClass 소속인 것 같지만 사실은 연관성이 거의 없다.


class OuterClass {
    val outerValue = 10
    val a = "Outside Nested class."

    inner class Inner {
        private val innerValue = 20
        fun callMe() = a // OutClass 의 변수 할당 가능
        fun printItems() {
            println("inner: $innerValue, outer: $outerValue")
        }
    }

    fun printItems() {
        val inner = Inner() // 객체 생성
        inner.printItems()
    }
}

fun main(args: Array<String>) {
    val outer = OuterClass() // 객체 생성
    outer.printItems()

    println("Using outer object: ${outer.Inner().callMe()}")

    val inner = OuterClass().Inner()
    println("Using inner object: ${inner.callMe()}")
}
 


https://www.bsidesoft.com/8218 에 더 많은 내용이 있으니 참조하면 도움된다.

블로그 이미지

Link2Me

,
728x90

자바에서는 A 클래스 안에 B 클래스를 정의하면 B 클래스는 자동으로 내부 클래스가 되었다.
하지만 코틀린에서는 반대다.
한 클래스안에 다른 클래스를 정의하면 기본적으로는 중첩 클래스가 되고,
내부 클래스로 만들고 싶다면 inner 키워드로 클래스를 선언해야 한다.
내부 클래스는 기본적으로 외부 클래스를 참조하게 되지만 중첩 클래스는 그렇지 않다.


class Outer {
    val a = "Outside Nested class."

    class Nested {
        // Outer Class 변수에 접근할 수 없다.
        val b = "Inside Nested class."
        fun callMe() = "Function call from inside Nested class."
    }
}

fun main(args: Array<String>) {
    // accessing member of Nested class
    println(Outer.Nested().b) // 자바의 static 중첩 클래스와 동일하다.

    // creating object of Nested class
    val nested = Outer.Nested()
    println(nested.callMe())
}
 




'안드로이드 > Kotlin 문법' 카테고리의 다른 글

[코틀린] ArrayList, mutableListOf  (0) 2021.06.14
[코틀린] Inner Class  (0) 2020.08.21
코틀린 클래스  (0) 2020.05.06
코틀린 null 안전과 예외  (0) 2020.05.05
코틀린 object  (0) 2020.01.19
블로그 이미지

Link2Me

,