2022-11-28

【Metabase】同步/更新 資料庫

有時我們異動了資料庫中的某些資料表,尤其當 Metabase 有使用到表中的欄位時,我們就必須 同步/更新,不然  Metabase 會無法正確顯示圖表。

 

登入 Metabase ,點擊右上角的 齒輪 → 系統管理

 

點擊上方功能表的 "資料庫"



點擊要 同步/更新 的資料庫


點擊 "開始同步資料庫綱要",這樣就完成了。


2022-11-20

【jQuery】簡單好用的表格元件 EasyUI datagrid

參考資料 ----


EsayUI datagrid 元件可以造出功能強大又好看的表格,只要添加自己撰寫對應的程式碼,甚至可以做到直接在表格內 新增/修改/刪除 的功能;非商業用途的可用免費版。

因為我的資料來源為網站上的資料庫,所以需要 server 端的程式以回應 client 端傳來的請求,又因為只做瀏覽用,如果看倌有要在表格內直接 新增/修改/刪除 數據的需要,請自行另外研究囉~。

先到 EasyUI 下載壓縮包,解壓後,找到名為 locale 的目錄,裡面有個 easyui-lang-zh_TW.js ,這是正體中文語言檔,將它上傳至您網站指定目錄,在本例為 /js/


前端的程式 client.php
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="keywords" content="" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>datagrid demo</title>
<meta name="description" content="datagrid demo" />
<link rel="shortcut icon" href="/image/icon.png" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>

<link rel="stylesheet" type="text/css" href="https://www.jeasyui.com/easyui/themes/default/easyui.css">
<link rel="stylesheet" type="text/css" href="https://www.jeasyui.com/easyui/themes/icon.css">
<link rel="stylesheet" type="text/css" href="https://www.jeasyui.com/easyui/themes/color.css">
<link rel="stylesheet" type="text/css" href="https://www.jeasyui.com/easyui/demo/demo.css">
<script type="text/javascript" src="https://www.jeasyui.com/easyui/jquery.easyui.min.js"></script>
<script type="text/javascript" src="/js/easyui-lang-zh_TW.js"></script><!-- 正體中文語言包-->
</head>
<body>
    <!-- <table id="table1" class="easyui-datagrid" style="width:1000px;height:auto"   auto 表示 表格的高度自動調整 -->
    <!-- 若屬性中設了 fixColumns="true" 欄寬就被固定住了, 並且表格底部不會顯示橫向捲軸 -->
    <table id="table1" class="easyui-datagrid" style="width:100%;height:400px"<!-- 可以用 px 設定表格 寬/高, 也可以用 % 螢幕比例 -->
        url="uber_rec.php"  <!-- 指定 server 端程式 -->
        pagination="true"   <!-- 要有頁數導覽元件 -->
        rownumbers="true"   <!-- 最左邊顯示列號 -->
        noWrap="true"       <!-- 欄位內的文字不折行 -->
        striped="true"      <!-- 單偶列不同背景色 -->
        singleSelect="true" <!-- 滑鼠點擊的列會高亮度顯示 -->
        pageList=[30,60,90] <!-- 每頁顯示的列數選項 -->
        pageSize=30 >       <!-- 預設每頁顯示 30 筆記錄 -->
        <thead>
            <tr>
                <th field="欄位1" width="180">欄位1 在 datagrid 的顯示名稱</th>
                <th field="欄位2" width="200">欄位2 在 datagrid 的顯示名稱</th>
                ...
                ...
            </tr>
        </thead>
    </table>
</div>
</body>
</html>

 


server 端的程式 get_rec.php
 
<?php
// 建立 PDO
$dsn = 'mysql:dbname=資料庫名;host=localhost;charset=utf8;';
$user = 'MySQL帳戶名';
$password = '密碼';
$options = array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'); 
try
    {   // 建立資料連線
        $pdo = new PDO($dsn, $user, $password, $options);
    }
catch (PDOException $e)
    {
        echo 'connection failed';
        exit;
    }

$result = array();

// 撈記錄總筆數
$sql = "SELECT COUNT(*) cnt FROM 資料表A";
$pdoStat = $pdo->prepare($sql);
$pdoStat->execute();
$rs = $pdoStat->fetch();
$result["total"] = $rs['cnt'];   // EasyUI 規定的 jason 元素名


$iRows = isset($_POST['rows']) ? intval($_POST['rows']) : 30;   // client 端傳來的一頁要顯示的記錄筆數, 預設 30
$iOffset = isset($_POST['page']) ? (intval($_POST['page'])-1)*$iRows : 0;   // client 端傳來的一頁要顯示第幾頁, 預設 0
$sql = "SELECT * FROM 資料表A ORDER BY 排序欄位 LIMIT $iRows OFFSET $iOffset";
$pdoStat = $pdo->prepare($sql);
$pdoStat->execute();
$rs = $pdoStat->fetchAll();

$recs = array();

if(count($rs)>0)
{
    foreach($rs as $row)
    {
        array_push($recs, ['欄位1'=>$row['欄位1'], '欄位2'=>$row['欄位2'], ...]);
    }
    $result["rows"] = $recs;  // EasyUI 規定的 jason 元素名
}
echo json_encode($result);
?>
 


2022-11-06

【PHP】Google Maps API -- 將地址轉換成經緯度座標

參考資料 ----

建立類別 GMap,用地址轉換經緯度

GMap.php
 
class GMap
{
    function getPageData($url)
    { 
        $ch = curl_init(); 
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);  
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect: '));
        curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); 
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_NOBODY, false);
        curl_setopt($ch, CURLOPT_FILETIME, true);
        curl_setopt($ch, CURLOPT_REFERER, $url);
         
        curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 4);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);
        curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)");
        //取得原始碼
        $result['data'] = curl_exec($ch);
        //$info_tmp = curl_getinfo($ch);
        //取得info資訊
        //$result['info'] = $info_tmp;
        unset($info_tmp);
        curl_close($ch);
          
        return $result;
    }


    /** 
     * 初始化 
     * @param $apikey 金鑰 
     */ 
    function __construct()
    {
        $this->apikey = '填入金鑰';
    } 


    /* 
     * 獲取地址經緯度 - 從google map
     */  
    public function getLatLng($addr='',$apikey='')
    {
        $apikey = ($apikey=='') ? $this->apikey : $apikey;
        $url = "https://maps.googleapis.com/maps/api/geocode/json?address=$addr&key=$apikey";
        $geocode = $this->getPageData($url);
        if(isset($geocode['data']))
            $geocode = $geocode['data'];
        else
            // 當 Google map 解析不了時,回應虛擬的經緯度
            $geocode = '{"results":[{"geometry":{"location":{"lat":-1,"lng":-1}}}]}';

        $output = json_decode($geocode); 
        $latitude = $output->results[0]->geometry->location->lat;
        $longitude = $output->results[0]->geometry->location->lng; 
     
        return array('lat'=>$latitude,'lng'=>$longitude);
    }
}
 


主程式 main.php
 
<?php
date_default_timezone_set('Asia/Taipei');
include('gmap.php');

$gmap = new GMap();
// 填入地址(高雄捷運美麗島站, 22.631754649712416, 120.30137231318399)
// 像美麗島站這類大地標,像上面明確的描述,通常也解析的出來
// 注意:地址字串內不要有空白
$data = $gmap->getLatLng('高雄市新興區中山一路115號');
echo "經度:".$data['lng'].",緯度:".$data['lat'];
?>
 




2022-10-23

【Kotlin】AdMob(API20(含)↑) 自適應橫幅 Adaptive Banner, 適 Android R(API30↑)

參考資料 ----


Smart Banner 將停用,全面以 Adaptive Banner 取代!

因應 Java 的改版, AdMob 也跟著更新,並改變了 取得 AdSize 的寫法

app 層級build.gradle 要使用最新版的 play-services-ads,目前(2022.10.23) 是 21.3.0

 
...
...
dependencies {
    ...
    ...
    
    implementation 'com.google.android.gms:play-services-ads:21.3.0'
    
    ...
    ...
}    
 


strings.xml
 
...
...

    < 測試的 AdMob app id -->
    <string name="app_id">ca-app-pub-3940256099942544~3347511713</string>
    < 測試的 AdMob 橫幅 id -->
    <string name="banner">ca-app-pub-3940256099942544/6300978111</string>

...
...
 


AndroidManifest.xml
 
    <application
        ...
        ... >

        <!-- App ID -->
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="@string/app_id" />
            
        ...
        ...
        <activity
                ...
                ...
        
 


原本的 layout.xml 是在佈局中直接放一個 adview,因為要動態決定 adview 的尺寸,就不這麼做了。
 
...
...

<com.google.android.gms.ads.adview
    ads:adsize="SMART_BANNER"
    ads:adunitid="@string/banner_ad_unit_id"
    android:id="@+id/adView"
    android:layout_alignparentbottom="true"
    android:layout_alignparentend="true"
    android:layout_alignparentleft="true"
    android:layout_alignparentright="true"
    android:layout_alignparentstart="true"
    android:layout_height="wrap_content"
    android:layout_width="match_parent" />

...
...
 

改成以 framelayout 做為 AdView 的容器(container)
 
...
...

<!-- 只要是 ViewGroup 層級皆可,ex:FrameLayout...,視您的需要自行變化 -->
<LinearLayout
    android:id="@+id/ad_view_container"
    android:layout_alignParentBottom="true"
    android:layout_centerInParent="true"
    android:layout_height="wrap_content"
    android:layout_width="match_parent" />
...
...
 


MainActivity.kt
 
...
...

class MainActivity : AppCompatActivity() 
{
    private lateinit var adView: AdView

    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        MobileAds.initialize(this) { }

        // 程式執行時,取得當時的螢幕寬度
        // 再動態決定 adView 的尺寸
        adView = AdView(this)
        ad_view_container.addView(adView)
        loadBanner()

    }


    // 取得螢幕尺寸
    private val adSize: AdSize
        get()
        {
            val iScreenWidth = getScreenWidth()
            val fDensity = getDensity()
            val adWidth = (iScreenWidth / fDensity).toInt()
            return AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(this, adWidth)
        }
        
        
    private fun getScreenWidth(): Int {
        val wm = application.getSystemService(WINDOW_SERVICE) as WindowManager
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            val windowMetrics = wm.currentWindowMetrics
            windowMetrics.bounds.width()
        } else {
            val displayMetrics = DisplayMetrics()
            wm.defaultDisplay.getMetrics(displayMetrics)
            displayMetrics.widthPixels
        }
    }

    private fun getDensity(): Float {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            val config: Configuration = application.resources.configuration
            config.densityDpi / 160f
        } else {
            val metrics = application.resources.displayMetrics
            metrics.density
        }
    }

    // 載入橫幅
    private fun loadBanner()
    {
        adView.adUnitId = getString(R.string.banner)
        // adView.adSize = adSize
        mAdView.setAdSize(adSize)
        val adRequest = AdRequest.Builder().build()
        adView.loadAd(adRequest)
    }
 


相關筆記 ----


2022-10-22

【Kotlin】偵測 Android 裝置的螢幕尺寸

 
...
...
    
when(this.resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK) {
    Configuration.SCREENLAYOUT_SIZE_SMALL -> Toast.makeText(this, "320x426", Toast.LENGTH_LONG).show()
    Configuration.SCREENLAYOUT_SIZE_NORMAL -> Toast.makeText(this, "320x470", Toast.LENGTH_LONG).show()
    Configuration.SCREENLAYOUT_SIZE_LARGE -> Toast.makeText(this, "480x640", Toast.LENGTH_LONG).show()
    Configuration.SCREENLAYOUT_SIZE_XLARGE -> Toast.makeText(this, "720x960", Toast.LENGTH_LONG).show()
    Configuration.SCREENLAYOUT_SIZE_UNDEFINED -> Toast.makeText(this, "undefined", Toast.LENGTH_LONG).show()
    else -> Toast.makeText(this, "I don't know", Toast.LENGTH_LONG).show()
}


...
...
 

手機


平板




2022-10-19

【Linux】CentOS 7 安裝 Git2

CentOS 7Git 版本為 1.8.x,若想使用 Git2,(例如想開發學習 Flutter 就必須更新到 Git2)必須另尋第三方的套件庫。


先移除舊版 git
 
[root]# yum  remove  git*
 


引入套件庫,目前有 2 個較多人採用的套件庫, 1 個是社群(IUS),一個是美國的資訊顧問公司(Endpoint)
 
# IUS 套件庫
[root]# yum  install  install https://repo.ius.io/ius-release-el7.rpm

# Endpoint 套件庫
# 本筆記採用 Endpoint 套件庫
[root]# yum  install  https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm
 

安裝 git2
 
# 很神奇,自動安裝 Endpoint 的,而不是 CentOS7 的 git
[root]# yum  install git
 


2022-10-10

【Kotlin】原始/陽春/樸實的 banner 橫幅廣告

參考資料 ----

AdMob 一直在變更 banner 的樣式, Smart Banner 即將棄用,取而代之的是 Adaptive Banners ,而 Adaptive Banners 因為要對應 Java 新版本 API 的因素也一直在變更寫法。

如果 不想 或 懶得 採用最新的廣告樣式,可以考慮 原始/陽春/樸實 的橫幅廣告。(本筆記結合了視圖綁定)

app 層級 build.gradle
 
...
...

android {
    
    buildFeatures {
        viewBinding true
    }
    
    ...
    ...
    
}

...
...

dependencies {

    // adMob
    implementation 'com.google.android.gms:play-services-ads:21.2.0'
    
}
 


strings.xml
 
<resources>
    
    ...
    ...

    <!-- adMob app id 測試 -->
    <string name="admob_app_id">ca-app-pub-3940256099942544~3347511713</string>
    <!-- 橫幅 測試 -->
    <string name="banner_ad_unit_id">ca-app-pub-3940256099942544/6300978111</string>

</resources>
 


AndroidManifest.xml
 
<manifest

    <!-- 存取網路連線 -->
    <uses-permission android:name="android.permission.INTERNET" />

    ...
    ...

    <application  ... >

        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="@string/admob_app_id" />

        ...
        ...

        <!-- AdMob 的 AdActivity -->
        <activity
            android:name="com.google.android.gms.ads.AdActivity"
            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
            android:theme="@android:style/Theme.Translucent" />

        ...
        ...

    </application>

</manifest>
 


activity_main.xml
 
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:ads="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="0dp"
    android:paddingLeft="0dp"
    android:paddingRight="0dp"
    android:paddingTop="0dp"
    tools:context=".MainActivity">
        
    <com.google.android.gms.ads.AdView
        android:id="@+id/adView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="true"
        ads:adSize="BANNER"
        ads:adUnitId="@string/banner_ad_unit_id" />

    <TextView
        android:id="@+id/txtHello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true"
        android:text="Hello World!" />

</RelativeLayout>
 
adSize  除了 BANNER 外,另有其他值可填入
大小 (寬 x 高)說明適用裝置AdSize 常值
320x50標準橫幅廣告手機和平板電腦BANNER
320x100大横幅手機和平板電腦LARGE_BANNER
300x250IAB 中矩形廣告手機和平板電腦MEDIUM_RECTANGLE
468x60IAB 完整橫幅廣告平板電腦FULL_BANNER
728x90IAB 超級橫幅廣告平板電腦LEADERBOARD

                           


MainActivity.kt
 
class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        binding.txtHello.text = "大家好"

        // AdMob 初始化
        MobileAds.initialize(this)
        binding.adView.loadAd(AdRequest.Builder().build())
    }
}
 

採用陽春型橫幅,缺點當然就是外觀顯示不夠漂亮,使用者體驗不好,因為尺寸都是固定的。

(手機直) BANNER 標準尺寸 320×50



(手機橫) BANNER 標準尺寸 320×50



大橫幅(LARGE_BANNER) 320×100



IAB 中矩形(MEDIUM_RECTANGLE) 300×250



平板 IAB 完整橫幅(FULL_BANNER) 468×60




平板(直) IAB 超級橫幅(LEADERBOARD) 728×90


平板(橫) IAB 超級橫幅(LEADERBOARD) 728×90



若希望不同尺寸螢幕的設備展示相應的橫幅,可以複製 activity_main.xml
/layout-w480dp//layout-w740dp/ 並將 adSize 屬性對應不同的橫幅。


/layout-w480dp/
 
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    ...
    ...
    tools:context=".MainActivity">
        
    <com.google.android.gms.ads.AdView
        android:id="@+id/adView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="true"
        ads:adSize="FULL_BANNER"
        ads:adUnitId="@string/banner_ad_unit_id" />

    ...
    ...

</RelativeLayout>
 


/layout-w740dp/
 
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    ...
    ...
    tools:context=".MainActivity">
        
    <com.google.android.gms.ads.AdView
        android:id="@+id/adView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="true"
        ads:adSize="LEADERBOARD"
        ads:adUnitId="@string/banner_ad_unit_id" />

    ...
    ...

</RelativeLayout>
 


方法二:
想到...不然自己土砲打造 "類自適應橫幅" 好了...😁

activity_main.xml
 
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="0dp"
    android:paddingLeft="0dp"
    android:paddingRight="0dp"
    android:paddingTop="0dp"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/txtHello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true"
        android:text="Hello World!" />

    <!-- 做為 自適應橫幅的 container -->
    <LinearLayout
        android:id="@+id/ad_view_container"
        android:layout_alignParentBottom="true"
        android:layout_centerInParent="true"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:orientation="horizontal" />

</RelativeLayout>
 


MainActivity.kt
 

...
...

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        binding.txtHello.text = "大家好"

        // AdMob 初始化
        MobileAds.initialize(this)

        // 橫幅廣告
        // 依螢幕尺寸變更橫幅的樣式
        val adView = AdView(this)
        when(this.resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK) {
            Configuration.SCREENLAYOUT_SIZE_XLARGE -> adView.setAdSize(AdSize.LEADERBOARD)
            Configuration.SCREENLAYOUT_SIZE_LARGE -> adView.setAdSize(AdSize.FULL_BANNER)
            Configuration.SCREENLAYOUT_SIZE_NORMAL -> adView.setAdSize(AdSize.LARGE_BANNER)
            else -> adView.setAdSize(AdSize.BANNER)
        }
        val lp = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT)
        lp.gravity = Gravity.CENTER
        adView.adUnitId = getString(R.string.banner_ad_unit_id)
        binding.adViewContainer.addView(adView,lp)     // 注意這裡 ad_view_container 的命名方式是 "駝峰式命名"
        adView.loadAd(AdRequest.Builder().build())

    }
}
 


相關筆記 ----


【Kotlin】視圖綁定 ViewBinding

參考資料 ----

以往要存取設定 layout 中的某個元件時,是透過 findViewById()

現在以 ViewBinding 的方式,就可以簡化,不需要上面的宣告了;AndroidStudio 3.6(含)以上版本才支援本功能。

app 層級 build.gradle
 
...
...

android {
    
    ...
    ...
    
    // 舊寫法
    // viewBinding {
    //     enabled = true
    // }
    // 依據 Android Studio 的版本,可能會提示您改成下面的寫法
    buildFeatures {
        viewBinding true
    }
    
    ...
    ...
 

activity_main.xml
 
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:ads="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/txtHello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:layout_alignParentEnd="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true" />

</RelativeLayout>
 

MainActivity.kt
 
...
...

// import [完整package name].databinding.[layout駝峰命名+Binding]
// 在本例中:
// 我們的 package name 為 com.example.vb
// layout 名為 activity_main.xml
import com.example.vb.databinding.ActivityMainBinding    // 這列在您輸入第 61 列的宣告時會自動提示生成

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        // setContentView(R.layout.activity_main)
        setContentView(view)
        
        // 變更元件 txtHello 內的文字
        binding.txtHello.text = "大家好"

    }
}
 


2022-10-03

【Android】Android13(API33) 起, APP 新增廣告 ID

參考資料 ----

因應 Google 愈來愈保護用戶隱私,自 Android 13(API33) 起,凡是 新建立的 或 要更新版本 的 APP 都要加入 廣告 ID

AndroidManifest.xml
 
<?xml version="1.0" encoding="utf-8"?>
<manifest 
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tw.whalin.plate">
    
    <uses-permission android:name="com.google.android.gms.permission.AD_ID"/>

    <application
        ...
        ...
 

若將 app 的 target(目標系統)版本定在 Android13AndroidManifest.xml 卻沒有加入廣告 ID 的授權宣告,即使在開發測試時期 app 用的是 AdMob 的測試帳號, app 執行時也不會顯示廣告。

要注意的是:現在 android app Google Play Store 的更新是採用 .aab,所以一旦上傳更新到 Android13 的版本就回不去了,沒法再降回 Android12,要想辦法符合 Google 的要求。