GWT

compatible 規範

符合下列條件的 class 才能在 GWT 內使用:

  • primitive data type

  • array(陣列元素也必須 compatible)

  • enum

  • JRE emulation class(包含可用 method)

  • 繼承 compatible class

  • 所有 field 都符合 compatible 原則

另外,synchronizedfinalize() 加了不會出問題,但是 GWT 會直接忽略。

(沒有考慮撰寫 emulate class 的招數)

reference:

RPC(serializable)規範

  • 滿足 compatible 規範

  • implement IsSerializable 或是 Serializable

  • 如果是某個 class 的 inner class,則必須得是 static class(可以不是 public)

  • 有 default(沒有參數)constructor,任何 access modifier 均可(private 也無所謂)。

    或是根本沒有任何 constructor。

  • 所有 field 都符合 RPC 原則。

    • 例外:final field(不會在 GWT RPC 中傳遞)

    • 例外:transient field(接收端會得到 null)

(沒有考慮 custom serialization 的招數)

備註:enum 只傳遞名稱。例如 FooEnum.FOO,就只會傳 FOO,裡頭的 field 值一概忽略。

reference:

client code 常見炸點 ###

假設 Foo 是符合 RPC 規範的 class,fooListArrayList<Foo>

  • Collections.unmodifiableList(fooList)

  • fooList.subList()

上述是因為回傳值的 instance 的 class 不符合 RPC 規範,所以無法過 GWT RPC。

雜項

  • 預設設定下,在 production mode 的 assert 是不會被觸發的。

    參考資料

Generator

因為哏太多而且不是正常的開發流程,所以另外開 GwtGenerator

Deferred Binding

when-type-is 的 class 沒有限定一定要 interface(官方文件好像也沒特別註明), 目前測試用 abstract class 也沒有問題。

JSNI

$wnd 是對應到 JS 的 window,也就是說 JSNI 的 $wnd.alert() 就等於 JS 的 window.alert()。 如果需要 JS 的全域變數,基本上就只能讓 $wnd 多加一個變數來處理, 實際上這等同在 host page 的 <script> 宣告一個變數:

//host page script block
var value = "foo";
const wtf = "wtf"; //用 const 宣告會取不到值,原因不明...... Orz
//in JSNI
$wnd.newGlobalArray = []; //JS 可以亂加 field 不要意外,延伸出來的 JS 哏這裡略過不提
$wnd.alert($wnd.value); //alert 視窗的訊息會是「foo」
$wnd.alert($wnd.wtf); //alert 視窗的訊息會是「undefine」

$doc 是對應到 JS 的 document。 注意,如果在 JSNI 裡頭直接操作 document,並不會是 host page 的 document instance, 而是 host page 裡頭某個 iframe 裡頭的 document。 這背後機制暫時不明... [死]

在 JSNI 裡頭用 [] 產生一個 class array 作為回傳值,基本上是不可行, 因為(以 Java 的觀點)缺乏 class 的資訊,而且到了執行期才會真正炸 cast 錯誤。 目前找到最簡單的方法就是自己用 Java 重新包一次... ==",例如:

//目的:不是要取得整個 entry,而是只要 entry 裡頭的 resource
public native FooJSO[] errorResult() /*-{
var result = [];
for (var i = 0; i < this.entry.legnth; i++) {
result.push(this.entry[i].resource);
}
return result;
}-*/;
// ======== //
private final native Foo result(int index) /*-{ return this.entry[index].resource; }-*/;
public List<FooJSO> getResult() {
ArrayList<FooJSO> result = new ArrayList<>();
for (int i = 0; i < getSize(); i++) {
result.add(result(i));
}
return result;
}

JSON 相關

client side 的 GWT 將 JSON 字串轉回(偽)Java instance 的議題。

注意 ######

請不要忘記這是 JS、這是 JS、這是 JS(很重要所以要講三次), 所以隨便 casting 是很合理的(謎之聲:教練我想寫 Java [淚目])。

Overlay Type ###

Overlay Type 的基礎是 JavaScriptObject。 繼承 JavaScriptObject 的 class 必須:

  • package 等級的 class modifier

  • protected 等級的 default constructor

  • 不能有 instance field

  • 要嘛 class 宣告成 final,要嘛每個 method(即使不是 native)宣告成 final

主要是用 JSNI 的 getter:

public final native String getId() /*-{ return this.id; }-*/;

對一個 JSON 字串作 JsonUtils.safeEval() 就會得到 JavaScriptObject 或其子孫,端看泛型怎麼指定。 後續呼叫 JavaScriptObject.cast() 也可以轉成想要的(繼承 JavaScriptObject)的 class。

轉換 tip ####

  • 務必在 production mode 作實際測試,會有 SDM 與 production mode 結果不同的情況。

  • 無法作 autoboxing

    • 所以即使 T 給 primitive type 的 wrapper,

      仍然無法用泛型 public native <T> getField(String fieldName) /*-{ return this[fieldName]; }-*/;

  • primitive type、enum(名稱一樣應該就 OK)可以直接轉換。

    • 無法轉換 long,compiler 會報錯,用 Long 就沒問題

    • 但是這很容易導致一個炸點,在實際使用時還是有可能被當成字串,例如:

      public final native int fakeInt() /*-{
      return this.foo == "" ? 0 : this.foo;
      }-*/;
      public final native Integer realInt() /*-{
      return @java.lang.Integer::valueOf(Ljava/lang/String;)(this.foo == "" ? "0" : this.foo);
      }-*/;
      public final void test() {
      Console.log((fakeInt() + 1) + " != " + (realInt() + 1));
      }

      呼叫 test() 會得到如「11 != 2」這種結果。 所以保險起見還是統統 拿去作雞精 轉成標準 class instance。 注意: 只有 production mode 才會炸,SDM 結果正常。

  • Date 轉換:

    private final native String fooDate() /*-{ return this.fooDate; }-*/;
    public final Date getFooDate() {
    //JSON 的 datetime 標準據說是用 ISO-8601
    //但是真的用這個 formate 卻無法處理如 "2012-12-21" 這種只有前半段符合的字串 =="
    //return DateTimeFormat.getFormat(PredefinedFormat.ISO_8601).parse(fooDate());
    //所以... 直接用 Date constructor,底層實作是直接用 JS 解... Orz
    return new Date(fooDate());
    //當然也可以直接寫在 JSNI 裡頭,不過寫起來好囉唆... [蓋牌]
    }

炸點 ####

假設收到這樣一個 JSON 字串

["ID", "DATA"]

然後這段程式碼就會炸奇怪的問題:

HashMap<String, JavaScriptObject> map = new HashMap<>();
JsArrayMixed array = JsonUtils.safeEval(json);
map.put(array.getString(0), array.getObject(1)); //這邊不會有事
JavaScriptObject foo = map.get(array.getString(0)); //這行會炸 casting 問題
WtfJSO wtf = new WtfJSO(array.getObject(1)); //constructor 參數的型態也是 JavaScriptObject
//上面這樣就不會出問題... WTF?

相關工具 ###

UI 相關

UiBinder

用 UiBinder 弄畫面,執行時一直炸找不到 fooWidget class 的錯誤(但是 Eclipse 沒有 compile error), 先單純用程式 new FooWidget() 出來,通常就會知道 FooWidget 錯在哪裡了。 (2.5.1 時常見不小心用了 generic 的 <> 就炸了,但是 Eclipse 不會炸錯誤 Orz)

ui:import ###

似乎是 ui.xml 中可以使用 enum 的唯一方法 (ui:import ref)。

I18N

gwt.xml ###

gwt.xml 當中要加上有要實作的語系

<extend-property name="locale" values="zh_CN"/>

至於設定 default locale 沒啥用

<set-property-fallback name="locale" value="en"/>

個人覺得沒啥用,倒是指定了語系就得要有 extend-property 然後也找得到對應的 properties 檔。 而且 default 的 properties 檔案還是不能少...... =="

<collapse-all-properties /> 好像不受影響(還是我誤會了這個設定值的意義...... Orz)

實際操作 ###

內建原生機制無法徵測 browser 的語系,必須在 host page 給 <meta>

或是在 URL 掛上 query string

http://127.0.0.1/hostPage.html?locale=zh_CN

會優先使用 query string 的設定值。

在 SDM 下,加上 query string 之後、甚至重新 compile 好像都不會順利切換語系。 另外開一個新的 tab 測試會比較實在。

Image

ImageResourceImage 如果要調整大小,

new Image(imgResource); //size 太大會留白
new Image(imgResource.getSafeUri()); //OK

沒有 attach 就不會觸發 LoadEvent,不過即使 setVisible(false) 也沒關係。

GWT 活跳跳的證據

註:Ray Cromwell 是 Googler、現任 GWT 委員長。